Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ Deduce template arguments based on other template arguments

Tags:

c++

templates

Suppose I have the following class:

template <class T, class U, class V> Foo
{
    ...
};

The template parameters have a distinct mapping, so I can deduce the other template arguments U and V based on what T is. For example, if T is double, U and V will always be some classes D1 and D2, and if T is float, U and V will always be some other classes F1 and F2.

With that in mind, is there a way I can pass in only one template argument, and have the compiler deduce the other two parameters?

I know the simple answer would be to just make these other classes templated as well and pass the template argument T to them, but I am not able to make these classes templated (they are auto-generated by a tool).

Ideally I would be able to use typedef or #define like so:

typedef Foo<double> Foo<double, D1, D2>
typedef Foo<float> Foo<float, F1, F2>

However these do not compile. I am wondering if there's a way to use template metaprogramming or template template parameters to solve this issue, but I can't seem to wrap my head those concepts, and I have a gut feeling there's probably an even simpler answer out there. Anybody have any ideas?

like image 414
thompsonja Avatar asked Feb 22 '13 13:02

thompsonja


People also ask

Can we pass Nontype parameters to templates?

Template classes and functions can make use of another kind of template parameter known as a non-type parameter. A template non-type parameter is a template parameter where the type of the parameter is predefined and is substituted for a constexpr value passed in as an argument.

What is template argument deduction in C++?

Class Template Argument Deduction (CTAD) is a C++17 Core Language feature that reduces code verbosity. C++17's Standard Library also supports CTAD, so after upgrading your toolset, you can take advantage of this new feature when using STL types like std::pair and std::vector.

Can there be more than one arguments to templates?

Can there be more than one argument to templates? Yes, like normal parameters, we can pass more than one data type as arguments to templates.

What is the difference between typename and class in template?

There is no difference between using <typename T> OR <class T> ; i.e. it is a convention used by C++ programmers.


2 Answers

The answer given by Angew shows you the right approach, but does not show you how to cope with situations where U and V cannot be deduced and must be provided by the instantiating client.

To handle this case, you could assign default arguments for the template parameters U and V:

struct D1 { }; struct D2 { };
struct F1 { }; struct F2 { };

// Primary template
template<typename T>
struct deduce_from
{
};

// Specialization for double: U -> D1, V -> D2
template<>
struct deduce_from<double>
{
    typedef D1 U;
    typedef D2 V;
};

// Specialization for float: U -> F1, V -> F2
template<>
struct deduce_from<float>
{
    typedef F1 U;
    typedef F2 V;
};

// Give defaults to U and V: if deduce_from is not specialized for
// the supplied T, and U or V are not explicitly provided, a compilation
// error will occur 
template<
    typename T,
    typename U = typename deduce_from<T>::U,
    typename V = typename deduce_from<T>::V
    >
struct Foo
{
    typedef U typeU;
    typedef V typeV;
};

And here is a simple program to test the correctness of the above solution:

#include <type_traits>

int main()
{
    static_assert(std::is_same<Foo<double>::typeU, D1>::value, "Error!");
    static_assert(std::is_same<Foo<double>::typeV, D2>::value, "Error!");
    static_assert(std::is_same<Foo<float>::typeU, F1>::value, "Error!");
    static_assert(std::is_same<Foo<float>::typeV, F2>::value, "Error!");

    // Uncommenting this will give you an ERROR! 
    // No deduced types for U and V when T is int
    /* static_assert(
        std::is_same<Foo<int>::typeU, void>::value, "Error!"
        ); */
    static_assert(
        std::is_same<Foo<int, bool, char>::typeU, bool>::value, "Error!"
        ); // OK
    static_assert(
        std::is_same<Foo<int, bool, char>::typeV, char>::value, "Error!"
        ); // OK
}
like image 69
Andy Prowl Avatar answered Sep 23 '22 21:09

Andy Prowl


You could get rid of U and V, like this:

template <typename T>
struct Foo
{
  typedef typename deduce_from<T>::U U;
  typedef typename deduce_from<T>::V V;
};

where deduce_from encapsulates the deduction process.

like image 45
Angew is no longer proud of SO Avatar answered Sep 21 '22 21:09

Angew is no longer proud of SO