如何在c++ 11中使用auto关键字返回任意类型

How to return arbitrary type in C++11 using auto keyword?

本文关键字:关键字 auto 返回 任意 类型 c++      更新时间:2023-10-16

我有一个类,看起来像这样:

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返回一些东西。但是,我不知道a prioi是否将返回的是doubleint。我想用这种方式实现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;
 }

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

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

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

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

不,你不能那样做。一个函数必须有一个在编译时已知的返回类型;当编译器可以从上下文中找出它必须是什么时,auto只是使您不必使用类型它。

可以使用带标签的联合,或者使用包装联合的类,比如Boost。变量,根据运行时决策保存不同类型的值。这可以使您获得具有可变返回类型的效果。

c++缺乏返回多个不相交类型的函数。

您可以通过使用boost::variant或类似的标记联合来模拟该语言特性,但这将使调用者的工作变成获得正确的类型。您还可以使用apply函子(boost::variant使用访问者语法)来处理两个重载(多亏了[](auto){} lambdas,用c++编写要容易得多)。

如果假设调用者知道类型,则可以根据类型公开两个不同的函数。如果您想要一个相对统一的接口,您可以使用基于类型的专门化的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<double, int>)。

您可以检查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);

A Hack

只能创建重载多个转换操作符的类:

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++)。

c++被设计成静态类型是为了安全。然而,你可以重新设计,从内到外使用多态性来获得你想要的效果。

这个想法是定义一个通用的数字类型,在查找映射中存储键/对象引用。双数列的子类别Int实现所需的操作,如字符串化,数学运算和转换,以允许精确的整数加减或方便的浮点乘法&部门。

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