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

You declared the same template 3 times, while you actually want specialisations. As far as I know you cannot specialize on return type directly (*). However, there is nothing that cannot be solved with an additional layer of indirection. You can do something along the line of:

#include <string>

template <int> struct return_type_tag {};
template <> struct return_type_tag<0> { using type = int; };
template <> struct return_type_tag<1> { using type = float; };
template <> struct return_type_tag<2> { using type = std::string; };

template <int x> typename return_type_tag<x>::type test();

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

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

    return 0;
}

(*) actually you can, with a small trick, see this answer. The only benefit of my approach is that it already worked pre-c++11.


The @StoryTeller and @formerlyknownas_463035818 have provided a well-explained template specialization way of doing it. Alternatively, one can combine the three functions to one single function using if-constexpr and with a decltype(auto) return in c++17.

#include <iostream>
#include <string>
#include <cstring>
using namespace std::literals;

template<int t>
constexpr decltype(auto) test() noexcept
{
    if constexpr (t == 0) return 8;
    else if constexpr (t == 1) return 8.8f;
    else if constexpr (t == 2) return "8.9"s;
}

(See live online)


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