c++c++20explicit-instantiation

Is it possible to explicitly instantiate a templated function that returns an unspecified type?


I'm trying to perform explicit instantiation of templated functions to improve accuracy of my code coverage results. I know how to do this when the return type is known. I can indirectly achieve the desired outcome through a definition of a wrapper function that returns void. Is it possible to achieve the outcome without this additional function definition?

#include <concepts>

template <typename T>
struct Gen {
    T operator()() const { return T{}; }
};
template <std::default_initializable T>
Gen<T> buildKnown() {
    return Gen<T>{};
}
template <std::default_initializable T>
std::invocable auto buildConstrained() {
    return Gen<T>{};
}
template <std::default_initializable T>
std::invocable auto buildInternal() /* -> decltype(??) */ {
    struct Local : Gen<T> {};
    return Local{};
}

// Perform explicit instantiation of each templated function.
template Gen<int> buildKnown<int>();        // works
template Gen<int> buildConstrained<int>();  // somewhat surprised this doesn't work.
template auto buildInternal<int>();         // not surprised this doesn't work

// Indirectly instantiate buildInternal through a wrapper function.
template <typename T> void buildInternalWrapper() { buildInternal<T>(); }
template void buildInternalWrapper<int>();

EDIT: Fixed type constraint (was originally std::invocable<int>) that makes the question more confusing.


Solution

  • Well, first of all, your deduced return type constraints are wrong. Consider for example this function template that you wrote:

    template <std::default_initializable T>
    std::invocable<T> auto buildConstrained() {
        return Gen<T>{};
    }
    

    What will happen if this is instantiated with T = int? It will become

    std::invocable<int> auto buildConstrained() {
        return Gen<int>{};
    }
    

    The placeholder type specifier std::invocable<int> auto means that the type needs to be deduced, and, if we call this deduced type U, then the constraint std::invocable<U, int> must be satisfied. So this function definition is only well-formed if std::invocable<Gen<int>, int> is satisfied. But it's not; Gen<int> is invocable with 0 arguments, not with 1 int argument.

    I'm guessing you meant:

    template <std::default_initializable T>
    std::invocable auto buildConstrained() {
        return Gen<T>{};
    }
    

    Having made such an amendment, we can now declare the explicit instantiation:

    template std::invocable auto buildConstrained<int>();
    

    An explicit instantiation of a function template that uses a placeholder type specifier must also use the same placeholder type specifier, not the deduced type.