Search code examples
c++templatesreturn-type

differentiate and nest function with template return type


What I want:

I have an issue with using 'nested'(?) templates as return type for my functions. The function declaration should look something like this:

// ### Generate.hpp
// return the template type
// input type is the same for all versions of generate()
template <typename T> T generate(Input in);

A few inline definitions for base types like int, bool are provided.

// ### Generate.hpp
template <> inline int generate<int>(Input in) {/*...*/}

Others ( e.g. custom classes) can be implemented separately.

// ### Custom_Class.hpp
#include "Generate.hpp" // can now make use of template

// ### Custom_Class.cpp
template <> custom::Class generate<custom::Class>(Input in) {/*...*/}

What's the issue

The issue arises when I want to use the function like this:

std::vector<int> x = generate<std::vector<int>>(...);

I would need to define something like this:

template <typename U> std::vector<U> generate(Input input); // differentiating via return type?

and the compiler (g++) won't let this fly.

error: call of overloaded ‘generate<std::vector<int>>(Input in)’ is ambiguous

Possible cause and solution.

C++ can't differentiate functions with only the return type. I would prefer functions with the form OutputType func(InputType input) over func (InputType input, OutputType& output), but I'm aware that I can solve this specific scenario in the latter for. There i could regularly overload generate() for stl container classes and such, which would enable the compile to differentiate.

The question finally boils down to if there is any template magic I can invoke to get what I originally wanted.

Thank you for your help.

Edit 1: more details on the use case

The specific use-case here is a function to further process tokens generated by a lexer.

template <typename T> std::pair<size_t, std::optional<T>> parse_tokens(const std::vector<Token>& tokens, size_t start = 0);

The functions gets a vector of Tokens (and a start offset) and must attempt to parse the beginning of the token sequenze into an object ot type T. That object (alongside the nuber of tokens parsed) is than to be returned.

The ideas that if it is 'known' that after the tokens from def [int] some_data = are followed by a list of ints, the functions could be called like res = parse_tokens<std::vector<int>>(tokens, list_starts_here). Having res.second for the optional value and res.first as the ammount of parsed tokens, needed to advance the parser index.

Edit2: Solution thanks to @Artyer

I needed a bit of fidling to make it work with the container class (generate<T>::generate() must be used, not just generate<T>(), otherwise the compiler can't deal with it). I haven't implemented this solution yet (only did some quick tests), but I'm confident that it can do what I want, how I want it. It should extend well and I can even have the desired functional syntax.

template <typename T>
struct generator;

template <typename T> T generate(std::string in) {
    return generator<T>::generate(in);
}

template <>
struct generator<int> {
    static int generate(std::string in) { return std::stoi(in); }
};

template <typename T>
struct generator<std::optional<T>> {
    static std::optional<T> generate(std::string in) 
    { 
        if (in == "nil") 
            return std::nullopt;
        else
            return generator<T>::generate(in);
    }
};

Thanks, again.


Solution

  • You can use partial specialization. Since functions can't be partially specialized, switch to a class template:

    template <typename T>
    struct generator;
    
    template <typename T> T generate(Input in) {
        return generator<T>::generate(in);
    }
    
    template <>
    struct generator<int> {
        static int generate(Input in) { /*...*/ }
    };
    
    template <typename T>
    struct generator<std::vector<T>> {
        static std::vector<T> generate(Input in) { /*...*/ }
    };
    
    // ### Custom_Class.hpp
    template <>
    struct generator<CustomClass> {
        static CustomClass generate(Input in);
    };
    
    // ### Custom_Class.cpp
    CustomClass generator<CustomClass>::generate(Input in) {
        return {};
    }