Search code examples
c++templatestemplate-specializationreturn-type

Return different types with different template parameter value (but same type)


What I want to do is to define 3 functions like these:

template<int t = 0> int test() { return 8; }
template<int t = 1> float test() { return 8.8; }
template<int t = 2> std::string test() { return "8.9"; }

int main()
{
    int a = test<0>();
    float b = test<1>();
    std::string c = test<2>();
    return 0;
}

They use the same type of template parameter but return different types.

I believe there must be some way to do that (like std::get<>() does it), but I can not find how to do it.


Solution

  • It looks to me like you are after function template specialization. Needing to provide a different implementation for each of the calls fits the bill. There is however one caveat, and it's that a specialization may not alter the signature of the primary template being specialized, only the implementation. This means we cannot do e.g.

    template<int t> int test(); // Primary
    template<> int test<0>() { return 8; } // OK, signature matches
    template<> float test<1>() { return 8.8; } // ERROR
    

    But we are not toasted yet. The signature of the specialization has to match the one that primary will get for a specific argument. So if we make the return type dependent on the template parameter, and resolve to the correct type, we could define our specialization just fine.

    template<int t> auto test() -> /* Magic involving t that resolves to int, float, string */;
    template<> int test<0>() { return 8; }
    template<> float test<1>() { return 8.8; }
    template<> std::string test<2>() { return "8.9"; }
    

    Does something like that exist? Yes, and you hinted at it. We can use std::tuple. It has a std::tuple_element utility that can map an integer to one of a type sequence (the tuple's elements). With a little helper, we can construct our code to work the way you wish:

    using types = std::tuple<int, float, std::string>;
    
    template<int t> auto test() -> std::tuple_element_t<t, types>;
    
    template<> int test<0>() { return 8; }
    template<> float test<1>() { return 8.8; }
    template<> std::string test<2>() { return "8.9"; }
    

    Now every specialization matches the signature the primary would end up with. And so we get an approval from our compiler.

    See it live