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?
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.
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 argument to templates? Yes, like normal parameters, we can pass more than one data type as arguments to templates.
There is no difference between using <typename T> OR <class T> ; i.e. it is a convention used by C++ programmers.
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
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With