c++c++23

C++ Template Function Ambiguity for Wrapper Type Specialization


I have a C++ function template:

#include <string_view>

template<typename T>
T get_gadget(std::string_view str);

I also added explicit specializations for some user-defined types, e.g., GadgetA and GadgetB:

template<>
GadgetA get_gadget<GadgetA>(std::string_view str) {
    // implementation
}

template<>
GadgetB get_gadget<GadgetB>(std::string_view str) {
    // implementation
}

Now, I want to make it work for a Wrapper type like this:

template<typename T>
Wrapper<T> get_gadget(std::string_view str) {
    auto underlying = get_gadget<T>(str);
    // implementation
}

However, when I compile, I get an ambiguous call error. I think this happens because the compiler cannot distinguish between the primary template and the Wrapper specialization.

I would like to solve this using concepts or template metaprogramming, without using void pointers, tag dispatching, or unnecessary boilerplate.

#include <iostream>
#include <string_view>

// Primary template (only declared)
template<typename T>
T get_gadget(std::string_view str);

// --- Some user-defined types ---
struct GadgetA {
    std::string name;
};
struct GadgetB {
    int value;
};

// --- Explicit specializations ---
template<>
GadgetA get_gadget<GadgetA>(std::string_view str) {
    return GadgetA{std::string(str)};
}

template<>
GadgetB get_gadget<GadgetB>(std::string_view str) {
    return GadgetB{static_cast<int>(str.size())};
}

// --- A generic Wrapper ---
template<typename T>
struct Wrapper {
    T inner;
};

// --- Generic overload for Wrapper<T> ---
template<typename T>
Wrapper<T> get_gadget(std::string_view str) {
    auto underlying = get_gadget<T>(str); // delegate
    return Wrapper<T>{underlying};
}

// --- Demo ---
int main() {
    auto a = get_gadget<GadgetA>("HelloA");
    auto b = get_gadget<GadgetB>("HelloB");
    auto wa = get_gadget<Wrapper<GadgetA>>("WrappedA");

    std::cout << "GadgetA: " << a.name << "\n";
    std::cout << "GadgetB: " << b.value << "\n";
    std::cout << "Wrapper<GadgetA>: " << wa.inner.name << "\n";
}

Solution

  • I'm going to assume that the intended call syntax for last overload is get_gadget<Wrapper<X>>("...") (not get_gadget<X>("..."), which would make no sense, as noted in the comments).

    In that case, the T template parameter of that function will be Wrapper<X>, not X.

    So you need something like:

    template <typename T>
    struct WrapperTraits
    {
        static constexpr bool is_wrapper = false;
    };
    
    template <typename T>
    struct WrapperTraits<Wrapper<T>>
    {
        static constexpr bool is_wrapper = true;
        using elem_type = T;
    };
    

    And then:

    template <typename T> requires WrapperTraits<T>::is_wrapper
    T get_gadget(std::string_view str)
    {
        auto underlying = get_gadget<typename WrapperTraits<T>::elem_type>(str);
        // implementation
    }