Question

I wish to create a function that returns a boxed tuple if more than one template argument is passed, and an unboxed value if only one template argument is passed.

For example, I would like foo<int>() to return an int and foo<int, float> to return a something with type std::tuple<int, float>.

All my attempts to achieve this effect have failed.

Consider the following approach using a 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...>
}

This will fail to compile because of the differing return types in the body of foo.

Alternatively, here is an attempt using decltype:

<template T>
T singular();

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

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

This will fail to compile as well because the ternary operator expects both branches to return the same type.

Finally, the naive approach using simple recursive unpacking fails as well:

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 }

This of course fails because the compiler cannot determine which overloaded function to call.

I can't see why something like this isn't possible in C++11 since all the information needed to determine the return type is available at compile time. And yet I'm struggling to see what tools would allow me to do this. Any help and suggestions would be appreciated.

Was it helpful?

Solution 2

Ugh, I literally figured out the answer after finally taking a break and grabbing a drink (this is after a couple hours of attempting to find a solution too!).

The answer is a modification of the last approach:

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 */ }

By using two dummy parameters, the compiler can unambiguously resolve which function is to be called.

OTHER TIPS

I usually use a struct for specializations:

#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>();
}

I would use tag dispatching.

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)...);
}

then I would add in perfect forwarding.

I find it easier to reason about overloads than about specializations.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top