返回变量类型取决于sizeof ...参数包

时间:2014-01-10 08:48:06

标签: c++ c++11 variadic-templates typetraits decltype

我希望创建一个函数,如果传递了多个模板参数,则返回一个盒装元组;如果只传递一个模板参数,则返回一个未装箱的值。

例如,我希望foo<int>()返回intfoo<int, float>以返回类型为std::tuple<int, float>的内容。

我实现这种效果的所有尝试都失败了。

使用 typetrait struct 考虑以下方法:

template<typename... T>
struct return_type {
    typedef std::tuple<T...> type;
};

template<>
struct return_type<int> {
    typedef int type;
};

// ... insert partial specializations for other supported primitive types

template<typename... T>
auto foo() -> typename return_type<T...>::type {
    if (sizeof...(T) == 1)
        return zap<T...>(); // Returns something of type T, where T is the first parameter
    else
        return bar<T...>(); // Assume this returns a std::tuple<T...>
}

由于foo正文中的返回类型不同,这将无法编译。

或者,这是尝试使用decltype

<template T>
T singular();

<template... T>
std::tuple<T...> multiple();

template <typename... T>
auto foo() -> decltype(sizeof...(T) == 1 ? singular() : multiple())
{
    ... // as above
}

这也将无法编译,因为三元运算符期望两个分支返回相同的类型。

最后,使用简单递归解包的天真方法也失败了:

template<typename T>
T foo() { return T{}; // return something of type T }

template<typename... T>
std::tuple<T...> foo() { return bar<T...>(); // returns a tuple }

这当然失败了,因为编译器无法确定要调用哪个重载函数。

我无法理解为什么在C ++ 11中无法实现这样的功能,因为在编译时可以获得确定返回类型所需的所有信息。然而,我很难看到哪些工具可以让我这样做。任何帮助和建议将不胜感激。

3 个答案:

答案 0 :(得分:4)

我通常使用结构进行专业化:

#include <iostream>
#include <tuple>

namespace Detail {
    template <typename...Ts>
    struct Foo {
        typedef std::tuple<Ts...> return_type;
        static return_type apply() { return return_type(); }
    };

    template <typename T>
    struct Foo<T> {
        typedef T return_type;
        static return_type apply() { return return_type(); }
    };
}
template <typename...Ts>
typename Detail::Foo<Ts...>::return_type foo() {
    return Detail::Foo<Ts...>::apply();
}

int main ()
{
    std::tuple<int, int> t = foo<int, int>();
    int i = foo<int>();
}

答案 1 :(得分:3)

呃,我终于休息一下并拿起一杯饮料(这是在经过几个小时试图找到解决方案之后)才得出答案。)

答案是对最后一种方法的修改:

template<typename T>
T foo() { return zap<T>(); /* returns a T */ }

template<typename T1, typename T2, typename... Ts>
std::tuple<T1, T2, Ts...> foo() { return bar<T1, T2, Ts...>(); /* returns a tuple */ }

通过使用两个伪参数,编译器可以明确地解析要调用的函数。

答案 2 :(得分:1)

我会使用标签调度。

template<class...Ts> struct many :std::true_type {};
template<class T>struct many :std::false_type {};

template<class...Ts> struct return_value {
  typedef std::tuple< typename std::decay<Ts>::type... > type;
};
template<class T>struct return_value : std::decay<T> {};

template<typename T>
T singular( T&& t ) {
  return std::forward<T>(t);
}
template<typename... Ts>
typename return_value<Ts...>::type multiple( Ts&&... ts ) {
  return { std::forward<Ts>(ts)... };
}

template<typename...T>
typename return_value<T...>::type worker(std::false_type, T&&...t ) {
  static_assert( sizeof...(T)==1, "this override is only valid with one element in the parameter pack" );
  return singular(std::forward<T>(t)...);
}

template<typename...Ts>
typename return_value<Ts...>::type worker(std::true_type, Ts&&...t) {
  return multiple(std::forward<Ts>(t)...);
}

template<class...Ts>
auto foo(Ts&&... t)
-> decltype(worker( many<Ts...>(), std::declval<Ts>()...) )
{
  return worker(many<Ts...>(), std::forward<Ts>(t)...);
}

然后我会添加完美转发。

我发现有关重载的理由比关于特殊化更容易。