Search code examples
c++templates

Matching template argument template parameter


I'm writing a kind of key-value database. The specs of the item to write are stored in a template, let's say

template<int address, typename T>
class ItemInfo{};

i.e ItemInfo<0x100, unsigned> means that some item which is of type unsigned, will be stored at the address 0x100.

Now I want a write a method that effectively does the store. It should receive both an ItemInfo and a value, something like this

template<typename ItemInfo, typename T>
void store(T value);

Note that ItemInfo should be a a template argument, not a function argument. In that method I need to know what the ItemInfo address and template type T are, so I can accomplish two things;

  1. To store the item at the address provided
  2. To only store the provided value if its type is the same as the one provided by the ItemsInfo type.

So the question is, how could I write such store method?

Notes: I can use C++20. The provided answer by @joergbrech is fine, but I wonder if there's a simpler solution, specially not involving concepts.


Solution

  • As you mention that you have C++20 available, you could define a concept that checks if a type is a realization of your template E and require that the template parameter of your function satisifies this concept.

    In addition, you need to be able to extract the template type of E. If you can modify E, you can add a typedef to E like so:

    #include <type_traits>
    
    template<typename T0>
    class E{
    public:
        using type = T0;
    };
    
    // SFINAE to check if a type is a template realization of E
    namespace details {
        template <typename T> struct is_E : std::false_type {};
        template <typename T> struct is_E<E<T>> : std::true_type {};
    }
    template <typename T> constexpr bool is_E_v = details::is_E<T>::value;
    
    // concept for E<T>
    template<typename T> concept is_E = requires { is_E_v<T>; };
    
    // function requires template parameter to be E
    template<is_E E>
    void f(typename E::type val){};
    
    int main()
    {
        f<E<double>>(2.0);
        return 0;
    }
    

    https://godbolt.org/z/9d3hc4WGG

    If you cannot modify E, you can add some meta-programming to extract the type:

    // meta-programming to extract the template parameter of E
    namespace details {
        template <typename T> struct E_value;
        template <typename T> struct E_value<E<T>> { using type = T; };
    }
    template <typename T> using E_value_type = typename details::E_value<T>::type;
    
    
    // function requires template parameter to be E
    template<is_E E>
    void f(E_value_type<E> val){};
    

    https://godbolt.org/z/GndM76M1n


    Addendum 1

    Here is a simplification, that does not require the definition of any additional meta-programming structs. You just need to provide an additional template parameter to f:

    #include <type_traits>
    
    template<typename T0>
    class E{};
    
    template <typename EType, typename T>
    concept is_E_with_type = requires { std::is_same_v<EType, E<T>>; };
    
    // function requires template parameter to be E
    template<typename EType, typename T>
    requires is_E_with_type<EType, T>
    void f(T val){};
    
    int main()
    {
        f<E<double>>(2.0);
        return 0;
    }
    

    https://godbolt.org/z/6Y8bd75Y3


    Addendum 2

    If you don't like concepts or want to support older C++ standards, you could use static_assert:

    #include <type_traits>
    
    template<typename T0>
    class E{};
    
    // function requires template parameter to be E
    template<typename EType, typename T>
    void f(T val){
        static_assert(std::is_same<EType, E<T>>::value, "template parameter passed in is not a template realization of E.");
    };
    
    int main()
    {
        f<E<double>>(2.0);
        //f<bool>(1); // static_assert fails
        return 0;
    }
    

    https://godbolt.org/z/nYjc3hT63