【问题标题】:How to return arbitrary type in C++11 using auto keyword?如何使用 auto 关键字在 C++11 中返回任意类型?
【发布时间】:2014-05-23 14:23:38
【问题描述】:

我有一个如下所示的课程:

class Container {
    public:
        Container(){
            Doubles["pi"] = 3.1415;
            Doubles["e"] = 2.7182;

            Integers["one"] = 1;
            Integers["two"] = 2;
        }

        // Bracket.cpp:23:9: error: 'auto' return without trailing return type
        // auto& operator[](const std::string&);
        auto& operator[](const std::string& key);

    private:
        std::map<std::string, double> Doubles;
        std::map<std::string, int> Integers;
};

我想重载operator[] 函数以根据传递的键从DoublesIntegers 返回一些内容。但是,我不知道优先返回的是double 还是int。我想用这种方式实现operator[]函数:

// Compiler error
// Bracket.cpp:30:1: error: 'auto' return without trailing return type
// auto& Container::operator[](const std::string& key){
auto& Container::operator[](const std::string& key){
    std::cout << "I'm returning the value associated with key: " 
              << key << std::endl;

    auto D_search = Doubles.find(key);
    if (D_search != Doubles.end()){
        std::cout << "I found my key in Doubles with value: " << 
            D_search->second << std::endl;

        return D_search->second;
    }
    else{
        auto I_search = Integers.find(key);
        if (I_search != Integers.end()){
            std::cout << "I found my key in Integers with value: " << 
                I_search->second << std::endl;

            return I_search->second;
        }
        else{
            std::cout << "I didn't find a value for the key." << std::endl;
        }
    }
}

有没有办法创建一个operator[] 函数来返回多种类型?

这是由这个简单的代码驱动的:

int main(){

    Container Bucket;

    double pi(Bucket["pi"]);

    std::cout << "The value of pi is: " << pi << std::endl;

    return 0;
 }

【问题讨论】:

  • 您不能仅根据返回类型重载。
  • auto 并不是突然允许函数具有多种返回类型的某种黑魔法。在这种情况下,由于int 可以转换为double,您可以只返回后者,但一般来说,您要求的内容是不可能的。创建单独的函数,或者如果您必须有一个函数,则返回 Boost.Variant 或类似的东西。

标签: c++ c++11 auto


【解决方案1】:

auto 作为返回类型的 C++11 版本仅允许您将返回类型声明推迟到声明函数参数之后:

template<typename T1, typename T2>
auto add(T1 x, T2 y) -> decltype(x + y) { return x + y; }

这里不能写decltype(x + y) add(...),因为编译器不知道xy是什么。

auto 的 C++14 版本允许编译器推导返回类型。它告诉编译器根据函数体推断出函数的返回类型,但它仍然是单一的返回类型,所以它仍然没有做你想做的事情。

【讨论】:

    【解决方案2】:

    不,你不能那样做。函数必须具有在编译时已知的单一返回类型; auto 只是让您不必键入它,因为编译器可以从上下文中找出它必须是什么。

    您可以使用tagged union 或包装Boost.Variant 的类来根据运行时决策保存不同类型的值。这可以为您提供具有可变返回类型的效果。

    【讨论】:

    • 请注意,Boost.Variant(目前)在 C++11 中的用处有限,因为它不能正确处理移动构造函数等。标记联合绝对是要走的路(Boost.Variant 接口是一个很好的接口),但您可能需要使用不同的实现(或编写自己的实现!),
    【解决方案3】:

    C++ 缺少返回多种不相交类型的函数。

    您可以使用boost::variant 或类似的标记联合来模拟该语言功能,但这会将其作为调用者的工作来获取正确的类型。您还可以使用处理两种重载所需的应用函子(boost::variant 使用访问者语法)(由于[](auto){} lambdas,在 C++1y 中更容易编写)。

    如果你假设调用者知道类型,你可以根据类型公开两个不同的函数。如果你想要一个相对统一的接口,你可以有一个基于类型的专门化的template 函数,但我只是有时会建议这样做。如果需要,异常或错误可以详细说明“您的类型错误”(即,如果您有一个规则,每个名称只映射到一种类型)。

    如果您不假定调用者知道类型,那么您必须返回一个对象,该对象允许对对象的类型进行某种查询(在运行时)。 boost::variantvisitor 是执行此操作的一种方法,因此他们在事后获得了类型安全的视图。 IUnknown/dynamic_cast 样式查询也很受欢迎,但说实话很不错。

    您还可以做很多技巧。

    您可以假设调用者知道标记数据的类型,并返回一个具有operator intoperator double 重载的伪引用,并执行某些操作(抛出?强制转换?断言?返回0?)如果调用者的类型错误。

    您可以使用异常来模拟多种返回类型:

    int get_foo( std::string bob ) {
      if (bob == "alice")
        throw 3.14;
      else
        return 7;
    }
    int main() {
      try {
        int x = get_foo("alice");
        std::cout << x << "\n";
      } except( double d ) {
        std::cout << d << "\n";
      }
    }
    

    但这太可怕了。

    可以想象一种支持这一点的语言:

    int|double get_foo( std::string bob ) {
      if (bob=="alice")
        return double{3.14};
      else
        return int(7);
    }
    int main() {
       int|double x = bob("alice");
       std::cout << x << "\n";
    }
    

    作为汇编级别,它将 2 个返回位置推入 bob(取决于它返回的返回类型)和 2 个(可能重叠)点以写入所述返回值,然后 bob 将写入到其中之一并返回到调用代码中的适当位置。调用者中之后的所有代码都必须“分叉”以处理两种可能的返回值类型。

    但该语言不是 C++。 C++ 中的所有表达式都必须是单一的已知类型,它不依赖于运行时参数,甚至不依赖于作为参数传递的 constexpr 参数。

    boost::variant 模拟一个不止一种类型的值,但它确实是一种类型 (boost::variant&lt;double, int&gt;),可让您访问包含的类型。

    【讨论】:

    • 是的,如果你用 auto 尝试你的小例子,那么你会得到不一致的 'auto' 扣除:'double' 然后 'int' 如果 auto 可以处理这个问题,那将是一个改进 - 也许在将来
    【解决方案4】:

    您可以检查Doubles 是否有键key,如果没有,请检查Integers,如果两者都不包含该键则抛出异常:

    auto Container::operator[](const std::string& key)
        -> decltype(find_double(key) ? Doubles->second : Integers->second);
    
    bool find_double(const std::string& key) const
    {
        return Doubles.find(key) != Doubles.end();
    }
    
    bool find_integer(const std::string& key) const
    {
        return Integers.find(key) != Integers.end();
    }
    

    然后在operator[] 里面你做:

    return find_double(key)  ? Doubles->second
         : find_integer(key) ? Integers->second
         : throw std::invalid_argument("Could not find key: " + key);
    

    【讨论】:

    • 要说“-&gt;double”需要输入很多字。
    【解决方案5】:

    一个黑客

    您只能创建一个重载多个转换运算符的类:

    struct FooReturnProxy {
        FooReturnProxy (int val) : val(val) {}
    
        operator int() const {
            return val*2;
        }
    
        operator std::string() const {
            return "Dizzle izzle dolizzle black boom shackalack tempizzle";
        }
    
        // and so on
    
    private:
        int val;
    };
    
    
    FooReturnProxy foo() {
        return FooReturnProxy(42);
    }
    
    int main () {
        int x = foo():
        std::string y = foo();
    }
    

    您也可以制作这些转换运算符模板。

    或者,您可以使用boost::any 来允许任何类型。

    然而...

    但是,鉴于在 C++ 社区中没有人真正使用这种方法,这充其量只能被视为一种 hack。真正的解决方案可能需要您在更高层次上审查您的设计。你真的需要吗?或者你只是想要它?如果只有后者,最好不要使用此处介绍的解决方案(当然,探索 C++ 除外)。

    【讨论】:

      【解决方案6】:

      为了安全起见,C++ 被设计为静态类型。但是,您可以重新设计将事物从里到外,以使用多态性来获得您想要的效果。

      这个想法是定义一个通用数字类型,存储在查找映射键/对象引用中。 double 和 int 的 number 子类实现了 stringify 和数学运算和转换等所需的操作,以允许精确的整数加法/减法或方便的浮点乘法和除法。

      面向对象设计的优势在于,如果以后您需要添加更多类号的实现类型,可能是为了无限精度,而不会影响使用对象方法开发的算法。对于桌面计算器类型的问题,这很可能过于复杂。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2021-12-19
        • 2023-03-31
        • 1970-01-01
        • 2015-12-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多