TLoe
TLoe

Reputation: 141

Return multiple values as std::tuple from template function

Trying to get this code to work, but strugling to figure out a solution. I know that I cannot overload on the return type, but not sure how to solve it then.

I'm getting this error, but not sure where it gets a std::basic_string non reference from? error C2440: 'return': cannot convert from 'std::tuple<std::basic_string<char,std::char_traits<char>,std::allocator<char>>,int &,float &>' to 'std::tuple<std::string &,int &,float &>'

#include <functional>
#include <iostream>
#include <string>
#include <tuple>

template <typename T>
T Get();

template <>
std::string Get() { return std::string{ "Hello" }; }

template <>
int Get(){ return 42; }

template <>
float Get() { return 42.0; }

template <typename T>
std::tuple<T> Generate()
{
    return std::make_tuple(Get<T>());
}

template <typename T1, typename... Ts>
std::tuple<T1,Ts...> Generate()
{
    auto t = std::make_tuple(Get<T1>());
    return std::tuple_cat(t, Generate<Ts...>());
}

struct A
{
    template <typename... Ts >
    operator std::tuple<Ts...> ()
    {
        return Generate<Ts...>();
    }
};

int main()
{
    std::string s;
    int i;
    float f;
    A as;
    std::tie(s, i, f) = as;
}

Upvotes: 1

Views: 1568

Answers (3)

TLoe
TLoe

Reputation: 141

Adding my own answer as I had a second issue that I failed to include in the original question. I needed indexes passed to the Get function so used this indices building trick that I found in a different post.

#include <functional>
#include <iostream>
#include <string>
#include <tuple>


template <std::size_t... Is>
struct Indices {};

template <std::size_t N, std::size_t... Is>
struct IndicesBuilder : IndicesBuilder<N - 1, N - 1, Is...>
{};

template <std::size_t... Is>
struct IndicesBuilder<0, Is...>
{
    using type = Indices<Is...>;
};

template <typename T>
T& Get(int index);

template <>
std::string& Get<std::string&>(int index) { std::cout << index << std::endl;  static std::string s{ "Hello" }; return s; }

template <>
int& Get<int&>(int index) { std::cout << index << std::endl; static int i{ 42 }; return i; }

template <>
float& Get<float&>(int index) { std::cout << index << std::endl; static float f{ 42 }; return f; }

template <typename... Ts, std::size_t... N>
std::tuple<Ts...> Generate(Indices<N...>)
{
    return std::tie(Get<Ts>(N)...);
}

struct A
{
    template <typename... Ts >
    operator std::tuple<Ts...> ()
    {
        constexpr std::size_t count = sizeof...(Ts);

        return Generate<Ts...>(typename IndicesBuilder<count>::type());
    }
};

int main()
{
    std::string s;
    int i;
    float f;
    A as;
    std::tie(s, i, f) = as;
    std::cout << s << " " << i << " " << f << std::endl;
}

Upvotes: 0

Mooing Duck
Mooing Duck

Reputation: 66932

std::tie(s, i, f) = as;

The left side is tuple<string&,int&,float&>, so the right hand side will try to convert to the same thing. Note those references. So for this to work, Generate will have to return matching types. So make_tuple has to go, that will have to be tie. But your Get functions also need to return references. Can do. While I was at it, I simplified the Generate call to not be recursive.

template <typename T>
T Get();

//note references, and a static variable, and explicitly saying T
template <>
std::string& Get<std::string&>() { static std::string a{ "Hello" }; return a;}

template <>
int& Get<int&>(){ static int a{42}; return a; }

template <>
float& Get<float&>() { static float a{42.0f}; return a; }

// note tie and non-recursive
template <typename ...T>
std::tuple<T...> Generate()
{
    return std::tie(Get<T>()...);
}

struct A
{
    template <typename... Ts >
    operator std::tuple<Ts...> ()
    {
        return Generate<Ts...>();
    }
};

int main()
{
    std::string s;
    int i;
    float f;
    A as;
    std::tie(s, i, f) = as;
    std::cout << "pass";
}

Proof of execution: http://coliru.stacked-crooked.com/a/036817509172da69

As SU3 notes, Generate is redundant. Could have been

struct A
{
    template <typename... Ts >
    operator std::tuple<Ts...> ()
    {
        return std::tie(Get<T>()...);
    }
};

Upvotes: 1

SU3
SU3

Reputation: 5399

How about this?

#include <iostream>
#include <string>
#include <tuple>

template <typename T>
T Get();

template <>
std::string Get<std::string>() { return "Hello"; }

template <>
int Get<int>(){ return 42; }

template <>
float Get<float>() { return 42.0; }

template <typename... Args>
std::tuple<Args...> Generate() {
  return { Get<Args>()... };
}

int main()
{
    std::string s;
    int i;
    float f;
    std::tie(s, i, f) = Generate<std::string,int,float>();

    std::cout << s << std::endl;
    std::cout << i << std::endl;
    std::cout << f << std::endl;
}

You can't specialize the function templates by just specifying the return type. Instead, you need to explicitly provide the template parameter.

Also, your use of std::tuple implies that you are using C++11 or above, so you may use parameter packs instead of recursion. This is just syntactic sugar, but it lets you write just one Generate() function implementation.

Upvotes: 0

Related Questions