Related
I'm learning about SFINE with class/struct template specialization and I'm a bit confused by the matching rule in a nuanced example.
#include <iostream>
template <typename T, typename = void>
struct Foo{
void foo(T t)
{
std::cout << "general";
}
};
template <typename T>
struct Foo<T, typename std::enable_if<std::is_integral<T>::value>::type>
{
void foo(T t)
{
std::cout << "specialized";
}
};
int main()
{
Foo<int>().foo(3);
return 0;
}
This behaves as expected and prints out specialized, however if I change the specialized function slightly to the following:
template <typename T>
struct Foo<T, typename std::enable_if<std::is_integral<T>::value, int>::type>
{
void foo(T t)
{
std::cout << "specialized";
}
};
then it prints out general. What's changed in the second implementation that makes it less 'specialized'?
Re-focus your eyeballs a few lines higher, to this part:
template <typename T, typename = void>
struct Foo{
This means that when this template gets invoked here:
Foo<int>().foo(3);
This ends up invoking the following template: Foo<int, void>. After all, that's what the 2nd template parameter is, by default.
The 2nd template parameter is not some trifle, minor detail that gets swept under the rug. When invoking a template all of its parameters must be specified or deduced. And if not, if they have a default value, that rescues the day.
SFINAE frequently takes advantage of default template parameters. Now, let's revisit your proposed template revision:
template <typename T>
struct Foo<T, typename std::enable_if<std::is_integral<T>::value, int>::type>
The effect of this specialization is that the 2nd template parameter in the specialization is int, not void.
But Foo<int> is going to still use the default void for the 2nd template parameter because, well, that's the default value of the 2nd template parameter. So, only the general definition will match, and not any specialization.
It's not that it's less specialized, it no longer matches the template instantiation. The second type parameter defaults to void, but since the std::enable_if now aliases int, the specialization no longer matches.
Suppose I have to following function:
template <typename Op, typename T> T foo(Op op) { }
Op is assumed to have the following method:
T Op::operator()(T&);
(this is a simple case; actually it might have more parameters but their types are known to me).
Now, I want to set a default for the T parameter. The problem is, in order to faux-invoke it (e.g. as in std::result_of<Op::operator()(int&)> I need to have the type of the parameter - which is exactly the type I'm missing.
Is it possible to determine T from Op? So that I may, for example, call:
foo( [](int& x){ return x++; } );
I'm interested in a C++11 solution; if you need a later-standard-version capability, that's also interesting (especially the explanation of why that is).
Note: If Op has multiple compatible operator()'s taking references to different types, I'm ok with having to specifyT` myself, of course - but the compilation should pass when I do that.
If the function object has exactly one signature, you can discover that signature by decltype(&T::operator()) and infer from that:
template<class T> struct X : X<decltype(&T::operator())> {}; // #1
template<class T> struct X<T(T&) const> { using type = T; }; // #2
template<class C, class M> struct X<M (C::*)> : X<M> {}; // #3
// add more specializations for mutable lambdas etc. - see example
template <typename Op, typename T = typename X<Op>::type>
T foo(Op op) { /* ... */ }
Example (C++11).
This is a series of type transformations, expressed as partial template specializations. From a lambda with anonymous type <lambda>, we extract its function call operator type at #1 giving a member function pointer type such as int (<lambda>::*)(int&) const, which matches #3 allowing us to discard the class type giving an abominable function type int(int&) const, which matches #2 allowing us to extract the argument and return type, exposed as X::type which is visible to the original instantiation of X via inheritance. Using partial specializations of the same template for different type computations is a code-golf trick and you might want to avoid it (and use more expressive names) in production code.
If on the other hand the function object has more than one signature (template parameters, default parameters, overloaded, hand-crafted function objects, etc.) then this will not work; you will need to wait for stronger forms of reflection to enter the language.
The existence of template functions shows the question is impossible to answer in general:
struct post_increment
{
template<typename T>
T operator()(T& t) const
{
return t++;
}
};
foo(post_increment{});
// or in C++14: foo([](auto& t) { return t++; });
The result of a function foo taking a mapping T& to T for some set of types T is itself polymorphic. In some cases, this is expressible even in C++11:
template<typename Op>
struct foo_result_t
{
template<typename T>
operator T() const
{
T t {};
op(t);
return t;
}
Op op;
};
template <typename Op>
foo_result_t<Op> foo(Op op)
{
return {op};
}
int i = foo(post_increment{}); // use it with an int
Another solution in case of exactly one signature.
Another... well... almost the same ecatmur's solution but based on a template function, to deduce the types, instead of a template class.
All you need is a declared function two declared functions that deduce the types (and return the same return type of the operator(); you can develop also another function to return, by example, std::tuple<Args...>, if you needs to extract the Args... types)
template <typename T, typename R, typename ... Args>
R deducer (R(T::*)(Args...) const);
template <typename T, typename R, typename ... Args>
R deducer (R(T::*)(Args...));
and a using (a little decltype() delirium) to extract the R type
template <typename T>
using RetType = decltype(deducer(std::declval<decltype(&T::operator())>()));
The following is a full C++11 example
#include <utility>
struct A
{
long operator() (int, char, short)
{ return 0l; }
};
template <typename T, typename R, typename ... Args>
R deducer (R(T::*)(Args...) const);
template <typename T, typename R, typename ... Args>
R deducer (R(T::*)(Args...));
template <typename T>
using RetType = decltype(deducer(std::declval<decltype(&T::operator())>()));
int main ()
{
auto f = [](int& x) { return x++; };
static_assert( std::is_same<RetType<A>, long>::value, "!" );
static_assert( std::is_same<RetType<decltype(f)>, int>::value, "!" );
}
If you wont to manage also volatile operators, you have to add other two deducer()
template <typename T, typename R, typename ... Args>
R deducer (R(T::*)(Args...) volatile const);
template <typename T, typename R, typename ... Args>
R deducer (R(T::*)(Args...) volatile);
I have very long parameter pack. I wonder is there any way to store the parameter pack and reuse it later. For example, if there are 2 templates:
template<class ... Types> struct A {};
template<class ... Types> struct B {};
I have a specialized type:
typedef A<int, int, float> A1_t;
Is there any operation can let me create a specialized type B which use the same parameter pack as A1_t? (B<int, int, float>). Is there any method to retrieve the <int, int, float> from A1_t or store it?
I want obtain a specialized type B1_t instead of creating the object of B1_t.
A and B describes completely different concept, so I cannot make B nested inside A.
moreover, I would also like to feed the parameter packs to specialize function templates.
template<class ...Ts>
C<Ts...> func1()
{
}
so I can directly call func1<int, int, float>()
It will be nice if I can do something like this:
template<typename T>
transform<B, T> func1()
{
}
next step would be something similar to this:
template<template<class...Ts> templ>
B<Ts...> func2(Ts ...args)
{
}
So I can do func2<A1_t>(1, 2, 3.0f) directly.
Something like this? Using a type transformation based on partial specialization:
#include<type_traits>
template<template<typename...> class, typename>
struct with_tmpl_args_of;
template<template<typename...> class OtherTmpl, template<typename...> class Tmpl, typename... Args>
struct with_tmpl_args_of<OtherTmpl, Tmpl<Args...>> {
using type = OtherTmpl<Args...>;
};
template<template<typename...> class OtherTmpl, typename T>
using with_tmpl_args_of_t = typename with_tmpl_args_of<OtherTmpl, T>::type;
// example
template<class ... Types> struct A {};
template<class ... Types> struct B {};
using A1_t = A<int, int, float>;
using B1_t = with_tmpl_args_of_t<B, A1_t>;
// test
static_assert(std::is_same_v<B1_t, B<int, int, float>>);
This is limited to class templates that do not use non-type template arguments. There is currently unfortunately no way to define template template parameters which accept both type and non-type template parameters in the same template template parameter's parameter.
Also beware of default arguments. This will not use OtherTmpl's default arguments, if one of Tmpl's default arguments matches that position in the template list and will fail if Tmpl's template list (including defaulted arguments) is larger than OtherTmpls.
Regarding the additional examples in your edit:
The second example works directly with the type transform I defined above:
template<typename T>
with_tmpl_args_of_t<B, T> func1()
{
}
The third one can be done like this:
template<typename A, typename... Ts>
with_tmpl_args_of_t<B, A> func2(Ts... args)
{
}
It guarantees that the return type has the same template arguments as A1_t, but it does accept all types as arguments, even if they don't match the types in the template arguments of A1_t. This should not usually be a problem. If the types are not convertible to the correct ones you will get an error at the point where you try the conversion.
If you must take the exact same types as in the template arguments of A1_t for function parameters, you can do something like (untested):
template<typename T>
struct func_with_tmpl_args_of;
template<template<typename...> class Tmpl, typename... Args>
struct func_with_tmpl_args_of<Tmpl<Args...>> {
template<typename F>
struct inner {
constexpr inner(F f) : f(std::move(f)) {}
constexpr static decltype(auto) operator()(Args... args) const {
return f(std::forward<Args>(args)...);
}
private:
F f;
};
};
// example
template<typename T>
constexpr inline auto func2 = func_with_tmpl_args_of<T>::inner{[](auto... args)
-> with_tmpl_args_of_t<B, T> {
// actual function body
}};
This is probably only a syntax problem.
So i have this template class :
template <typename String, template<class> class Allocator>
class basic_data_object
{
template<typename T>
using array_container = std::vector<T, Allocator<T>>;
};
And another one :
template <typename String, template<class> class Allocator, typename T>
struct get_data_object_value
{
};
Now i want to specialize the second one's T parameter with the first one's inner typedef array_container for any given type.
template <typename String, template<class> class Allocator, typename T>
struct get_data_object_value
<String, Allocator,
typename basic_data_object<String, Allocator>::template array_container<T>>
{
};
But this specialization doesn't seem to be matched when i pass an std::vector as the last parameter.
If i create a temporary hard coded typedef:
typedef basic_data_object<std::string, std::allocator<std::string>> data_object;
And use it for the specialization, everything works :
template <typename String, template<class> class Allocator, typename T>
struct get_data_object_value
<String, Allocator,
data_object::template array_container<T>>
{
};
What did i miss ? :)
Alternatively what is the best (smallest / cleanest) way to make this work ?
The C++ standard says, in [temp.class.spec.match] paragraph 2:
A partial specialization matches a given actual template
argument list if the template arguments of the partial
specialization can be deduced from the actual template
argument list (14.8.2).
14.8.2 is [temp.arg.deduct] i.e. the clause describing template argument deduction for function templates.
If you modify your code to use a similar function template and attempt to call it, you will see that the arguments cannot be deduced:
template <typename String, typename T>
void deduction_test(String,
typename basic_data_object<String, std::allocator>::template array_container<T>)
{ }
int main()
{
deduction_test(std::string{}, std::vector<int, std::allocator<int>>{});
}
(I removed the Allocator parameter, since there's no way to pass a template template parameter as a function argument and in the basic_data_object type it's a non-deduced context, I don't believe it affects the result.)
Both clang and GCC say they cannot deduce T here. Therefore the partial specialization will not match the same types used as template arguments.
So I haven't really answered the question yet, only clarified that the reason is in the rules of template argument deduction, and shown an equivalence with deduction in function templates.
In 14.8.2.5 [temp.deduct.type] we get a list of non-deduced contexts that prevent deduction, and the following rule in paragraph 6:
When a type name is specified in a way that includes a non-deduced context, all of the types that comprise that type name are also non-deduced.
Since basic_data_object<String, Allocator> is in a non-deduced context (it is a nested-name-specifier, i.e. appears before ::) that means the type T is also non-deduced, which is exactly what Clang and GCC tell us.
With your temporary hardcoded typedef there is no non-deduced context, and so deduction for T succeeds using the deduction_test function template:
template <typename String, typename T>
void deduction_test(String,
typename data_object::template array_container<T>)
{ }
int main()
{
deduction_test(std::string{}, std::vector<int, std::allocator<int>>{}); // OK
}
And so, correspondingly, your class template partial specialization can be matched when it uses that type.
I don't see a way to make it work without changing the definition of get_data_object_value, but if that's an option you can remove the need to deduce the array_container type and instead use a trait to detect whether a type is the type you want, and specialize on the result of the trait:
#include <string>
#include <vector>
#include <iostream>
template <typename String, template<class> class Allocator>
class basic_data_object
{
public:
template<typename T>
using array_container = std::vector<T, Allocator<T>>;
template<typename T>
struct is_ac : std::false_type { };
template<typename T>
struct is_ac<array_container<T>> : std::true_type { };
};
template <typename String, template<class> class Allocator, typename T, bool = basic_data_object<String, Allocator>::template is_ac<T>::value>
struct get_data_object_value
{
};
template <typename String, template<class> class Allocator, typename T>
struct get_data_object_value<String, Allocator, T, true>
{
void f() { }
};
int main()
{
get_data_object_value<std::string,std::allocator,std::vector<short>> obj;
obj.f();
}
This doesn't really scale if you wanted several class template partial specializations, as you would need to add several bool template parameters with default arguments.
For some reason, the problem seems to stem from the double level of templates. I'll leave you check the 3 test cases below, they are simple:
Remove the template arguments of First: works as expected
Make First a template, but the inner type a plain one: works as expected
Make both First and the inner type templates: compiles but the output is unexpected
Note: the template template parameter Allocator is useless to reproduce the issue, so I left it out.
Note: both GCC (ideone's version, 4.8.1 I believe) and Clang (Coliru version, 3.4) compile the code, and yet produce the same baffling result
From the 3 above examples, I deduce:
that this is NOT a non-deducible context issue; otherwise why would (2) work ?
that this is NOT an alias issue; otherwise why would (1) work ?
And therefore that either the problem is much hairier than the current hints would make us believe OR that both gcc and Clang have a bug.
EDIT: Thanks to Jonathan Wakely who patiently educated me enough that I could finally understand both the Standard wording related to this case and how it applied. I will now attempt to explain this (again) in my own words. Please refer to Jonathan's answer for the exact Standard quotes (it all sits in [temp.deduct.type])
When deducing template parameters (Pi), whether for functions or classes, the deduction is done independently for each and every argument.
Each argument need provide zero or one candidate Ci for each parameter; if an argument would provide more than one candidate, it provides none instead.
Thus, each argument produces a dictionary Dn: Pi -> Ci which maps a subset (possibly empty) of the template parameters to be deduced to their candidate.
The dictionaries Dn are merged together, parameter by parameter:
if only one dictionary has a candidate for a given parameter, then this parameter is accepted, with this candidate
if several dictionaries have the same candidate for a given parameter, then this parameter is accepted, with this candidate
if several dictionaries have different incompatible (*) candidates for a given parameter, then this parameter is rejected
If the final dictionary is complete (maps each and every parameter to an accepted candidate) then deduction succeeds, otherwise it fails
(*) there seems to be a possibility for finding a "common type" from the available candidates... it is of no consequence here though.
Now we can apply this to the previous examples:
1) A single template parameter T exists:
pattern matching std::vector<int> against typename First::template ArrayType<T> (which is std::vector<T>), we get D0: { T -> int }
merging the only dictionary yields { T -> int }, thus T is deduced to be int
2) A single template parameter String exists
pattern matching std::vector<int> against String, we get D0: { String -> std::vector<int> }
pattern matching std::vector<int> against typename First<String>::ArrayType we hit a non-deducible context (many values of String could fit), we get D1: {}
merging the two dictionaries yields { String -> std::vector<int> }, thus String is deduced to be std::vector<int>
3) Two template parameters String and T exist
pattern matching std::vector<char> against String, we get D0: { String -> std::vector<char> }
pattern matching std::vector<int> against typename First<String>::template ArrayType<T> we hit a non-deducible context, we get D1: {}
merging the two dictionaries yields { String -> std::vector<char> }, which is an incomplete dictionary (T is absent) deduction fails
I must admit I had not considered yet that the arguments were resolved independently from one another, and therefore than in this last case, when computing D1 the compiler could not take advantage of the fact that D0 had already deduced a value for String. Why it is done in this fashion, however, is probably a full question of its own.
Without the outer template, it works, as in it prints "Specialized":
#include <iostream>
#include <vector>
struct First {
template <typename T>
using ArrayType = std::vector<T>;
};
template <typename T>
struct Second {
void go() { std::cout << "General\n"; }
};
template <typename T>
struct Second < typename First::template ArrayType<T> > {
void go() { std::cout << "Specialized\n"; }
};
int main() {
Second < std::vector<int> > second;
second.go();
return 0;
}
Without the inner template, it works, as in it prints "Specialized":
#include <iostream>
#include <vector>
template <typename String>
struct First {
using ArrayType = std::vector<int>;
};
template <typename String, typename T>
struct Second {
void go() { std::cout << "General\n"; }
};
template <typename String>
struct Second < String, typename First<String>::ArrayType > {
void go() { std::cout << "Specialized\n"; }
};
int main() {
Second < std::vector<int>, std::vector<int> > second;
second.go();
return 0;
}
With both, it fails, as in it prints "General":
#include <iostream>
#include <vector>
template <typename String>
struct First {
template <typename T>
using ArrayType = std::vector<T>;
};
template <typename String, typename T>
struct Second {
void go() { std::cout << "General\n"; }
};
template <typename String, typename T>
struct Second < String, typename First<String>::template ArrayType<T> > {
void go() { std::cout << "Specialized\n"; }
};
int main() {
Second < std::vector<char>, std::vector<int> > second;
second.go();
return 0;
}
The answer of Jonathan Wakely gives the reason why your code does not work.
My answer shows you how to solve the problem.
In your example, the container type over which you want to specialize is defined outside of basic_data_object thus you can of course use it directly in your specialization:
template <typename S, template<class> class A, typename T>
struct get_data_object_value<S,A,std::vector<T,A>>
{ };
This definitely conforms with the standard and works with all compilers.
In the case where the type is defined in basic_data_object, you can move it out of the class.
Example: Instead of
template<typename S, template<class> class A>
struct a_data_object
{
template<typename T>
struct a_container
{ };
};
write this:
template<typename S, template<class> class A, typename T>
// you can perhaps drop S and A if not needed...
struct a_container
{ };
template<typename S, template<class> class A, typename T>
struct a_data_object
{
// use a_container<S,A,T>
};
Now you can specialize with:
template <typename S, template<class> class A, typename T>
struct get_data_object_value<S,A,a_container<S,A,T>>
{ };
Note: The next "solution" is apparently a bug with GCC 4.8.1.
If the container is only defined in an enclosing template and can not be moved out you can do this:
Get the container type out of basic_data_object:
template<typename S, template<class> class A, typename T>
using bdo_container = basic_data_object<S,A>::array_container<T>;
Write a specialization for this type:
template <typename S, template<class> class A, typename T>
struct get_data_object_value<S,A,bdo_container<S,A,T>>
{ };
Alternatively what is the best (smallest / cleanest) way to make this work?
Arguably, it is:
Write a SFINAE trait template Tr<String,Allocator,T> that determines whether T is the
same as basic_data_object<String,Allocator>::array_container<T::E>
for some type E - if such there be - that is T::value_type.
Provide template get_data_object_value with a 4th parameter
defaulting to Tr<String,Allocator,T>::value
Write partial specializations of get_data_object_value instantiating that
4th parameter as true, false respectively.
Here is a demo program:
#include <type_traits>
#include <vector>
#include <iostream>
template <typename String, template<class> class Allocator>
struct basic_data_object
{
template<typename T>
using array_container = std::vector<T, Allocator<T>>;
};
template<typename T, typename String, template<class> class Allocator>
struct is_basic_data_object_array_container
/*
A trait template that has a `static const bool` member `value` equal to
`true` if and only if parameter type `T` is a container type
with `value_type E` s.t.
`T` = `basic_data_object<String,Allocator>::array_container<T::E>`
*/
{
template<typename A>
static constexpr bool
test(std::is_same<
A,
typename basic_data_object<String,Allocator>::template
array_container<typename A::value_type>
> *) {
return std::is_same<
A,
typename basic_data_object<String,Allocator>::template
array_container<typename A::value_type>
>::value;
}
template<typename A>
static constexpr bool test(...) {
return false;
}
static const bool value = test<T>(nullptr);
};
template <
typename String,
template<class> class Allocator,
typename T,
bool Select =
is_basic_data_object_array_container<T,String,Allocator>::value
>
struct get_data_object_value;
template <
typename String,
template<class> class Allocator,
typename T
>
struct get_data_object_value<
String,
Allocator,
T,
false
>
{
static void demo() {
std::cout << "Is NOT a basic_data_object array_container" << std::endl;
}
};
template <
typename String,
template<class> class Allocator,
typename T>
struct get_data_object_value<
String,
Allocator,
T,
true
>
{
static void demo() {
std::cout << "Is a basic_data_object array_container" << std::endl;
}
};
#include <list>
#include <memory>
using namespace std;
int main(int argc, char **argv)
{
get_data_object_value<string,allocator,std::vector<short>>::demo();
get_data_object_value<string,allocator,std::list<short>>::demo();
get_data_object_value<string,allocator,short>::demo();
return 0;
}
Built with gcc 4.8.2, clang 3.4. Output:
Is a basic_data_object array_container
Is NOT a basic_data_object array_container
Is NOT a basic_data_object array_container
VC++ 2013 will not compile this for lack of constexpr support. To accommodate that
compiler the following less natural implementation of the trait may be used:
template<typename T, typename String, template<class> class Allocator>
struct is_basic_data_object_array_container
{
template<typename A>
static
auto test(
std::is_same<
A,
typename basic_data_object<String, Allocator>::template
array_container<typename A::value_type>
> *
) ->
std::integral_constant<
bool,
std::is_same<
A,
typename basic_data_object<String, Allocator>::template
array_container<typename A::value_type>
>::value
>{}
template<typename A>
static std::false_type test(...);
using type = decltype(test<T>(nullptr));
static const bool value = type::value;
};
Edit: This answer only works because of a bug in GCC 4.8.1
Your code works as expected if you drop the keyword template in your specialization:
template <typename String, template<class> class Allocator, typename T>
struct get_data_object_value
{
void foo() { std::cout << "general" << std::endl; }
};
template <typename String, template<class> class Allocator, typename T>
struct get_data_object_value
<String, Allocator,
typename basic_data_object<String, Allocator>::array_container<T>>
// ^^^^^^ no template!
{
void foo() { std::cout << "special" << std::endl; }
};
Example tested with GCC 4.8.1:
int main() {
get_data_object_value<std::string,std::allocator,std::vector<int>> obj;
obj.foo(); // prints "special"
}
I wondering if something similar to this is possible. Basically, I have a templated class that occasionally takes objects of templated classes. I would like to specialize it (or just a member function)for a specific templated class, but the 'generic' form of that class.
template<typename T, typename S>
class SomeRandomClass
{
//put something here
};
template<typename T>
class MyTemplateClass
{
void DoSomething(T & t) {
//...something
}
};
template<>
void MyTemplateClass< SomeRandomClass<???> >::DoSomething(SomeRandomClass<???> & t)
{
//something specialized happens here
}
Replacing the question marks with appropriate types (double, etc) works, but I would like it to remain generic. I don't know what to put there, as any types wouldn't have been defined. I've looked around, and learned about template template parameters, and tried various combinations to no avail. Thanks for the help!
It's possible to specialize the class like this
template <>
template <typename T,typename S>
class MyTemplateClass <SomeRandomClass<T,S> >
{
void DoSomething(SomeRandomClass<T,S>& t) { /* something */ }
};
It's not possible to specialize just the member method, because the specialization is on the class as a whole, and you have to define a new class. You can, however, do
template <>
template <typename T,typename S>
class MyTemplateClass <SomeRandomClass<T,S> >
{
void DoSomething(SomeRandomClass<T,S>& t);
};
template <>
template <typename T,typename S>
void MyTemplateClass<SomeRandomClass<T,S> >::DoSomething(SomeRandomClass<T,S>& t)
{
// something
}
to split up the declaration and definition.
I'm not completely sure why #Ryan Calhoun specialized the way he did but here's a more terse example:
// class we want to specialize with later on
template<typename T, typename S>
struct SomeRandomClass
{
int myInt = 0;
};
// non-specialized class
template<typename T>
struct MyTemplateClass
{
void DoSomething(T & t)
{
std::cout << "Not specialized" << std::endl;
}
};
// specialized class
template<typename T, typename S>
struct MyTemplateClass< SomeRandomClass<T, S> >
{
void DoSomething(SomeRandomClass<T,S> & t)
{
std::cout << "Specialized" << std::endl;
}
};
You can see that you don't need the redundant syntax used in the accepted answer:
template<>
template<typename T, typename S>
Working Demo
Alternative
You can use type_traits and tag-dispatch within your non-specialized class to specialize just the function.
Let's first make a concept for is_random_class:
// concept to test for whether some type is SomeRandomClass<T,S>
template<typename T>
struct is_random_class : std::false_type{};
template<typename T, typename S>
struct is_random_class<SomeRandomClass<T,S>> : std::true_type{};
And then let's declare our MyTemplateClass again, but this time not templated (because we're not specializing) so we'll call it MyNonTemplatedClass:
class MyNonTemplatedClass
{
public:
template<typename T>
void DoSomething(T & t)
{
DoSomethingHelper(t, typename is_random_class<T>::type());
}
// ...
Notice how DoSomething is now templated, and it's actually calling a helper instead of implementing the logic itself?
Let's break down the line:
DoSomethingHelper(t, typename is_random_class<T>::type());
t is as-before; we're passing along the argument of type T&
typename is_random_class<T>::type()
is_random_class<T> is our concept, and since it derives from std::true_type or std::false_type it will have a ::type defined within the class (Google for "type traits")
::type() 'instantiates' the type specified by is_random_class<T>::type. I say it in quotation marks because we're really going to throw that away as we see later
typename is required because the compiler doesn't know that is_random_clas<T>::type actually names a type.
Now we're ready to look at the rest of MyNonTemplatedClass:
private:
//use tag dispatch. If the compiler is smart it won't actually try to instantiate the second param
template<typename T>
void DoSomethingHelper(T&t, std::true_type)
{
std::cout << "Called DoSomething with SomeRandomClass whose myInt member has value " << t.myInt << std::endl;
}
template<typename T>
void DoSomethingHelper(T&t, std::false_type)
{
std::cout << "Called DoSomething with a type that is not SomeRandomClass\n";
}
};
Full Working Demo v2 Here
Notice that our helper functions are named the same, but overloaded on the second parameter's type. We don't give a name to the parameter because we don't need it, and hopefully the compiler will optimize it away while still calling the proper function.
Our concept forces DoSomethingHelper(T&t, std::true_type) only if T is of type SomeRandomClass, and calls the other for any other type.
The benefit of tag dispatch
The main benefit of tag dispatch here is that you don't need to specialize your entire class if you only mean to specialize a single function within that class.
The tag dispatching will happen at compile time, which you wouldn't get if you tried to perform branching on the concept solely within the DoSomething function.
C++17
In C++17, this problem becomes embarrassingly easy using variable templates (C++14) and if constexpr (C++17).
We use our type_trait to create a variable template that will give us a bool value of true if the provided type T is of type SomeRandomClass, and false otherwise:
template<class T>
constexpr bool is_random_class_v = is_random_class<T>::value;
Then, we use it in a if constexpr expression that only compiles the appropriate branch (and discards the other at compile-time, so the check is at compile-time, not run-time):
struct MyNonTemplatedClass
{
template<class T>
void DoSomething(T& t)
{
if constexpr(is_random_class_v<T>)
std::cout << "Called DoSomething with SomeRandomClass whose myInt member has value " << t.myInt << std::endl;
else
std::cout << "Called DoSomething with a type that is not SomeRandomClass\n";
}
};
type-traits were a way to simulate this without needing a class specialization.
Note that is_random_class here is a stand-in for an arbitrary constraint. In general, if you're only checking for a single nontemplated type, prefer a normal overload because it's more efficient on the compiler.
Demo
C++20
In C++20, we can take this a step further and use a constraint instead of if constexpr by using a requires clause on our templated member function. The downside is that we again move back to two functions; one that matches the constraint, and another that doesn't:
struct MyNonTemplatedClass
{
template<class T> requires is_random_class_v<T>
void DoSomething(T& t)
{
std::cout << "Called DoSomething with SomeRandomClass whose myInt member has value " << t.myInt << std::endl;
}
template<class T> requires !is_random_class_v<T>
void DoSomething(T&)
{
std::cout << "Called DoSomething with a type that is not SomeRandomClass\n";
}
};
Demo
Also in C++ 20, we could explicitly encode a concept and use abbreviated template syntax:
template<class T>
concept IsRandomClass = is_random_class_v<T>;
template<class T>
concept IsNotRandomClass = !is_random_class_v<T>;
// ...
template<IsRandomClass T>
void DoSomething(T& t)
{ /*...*/}
template<IsNotRandomClass T>
void DoSomething(T&)
{ /*...*/}
Demo
All you need to do is just template on what you want to keep generic. Taking what you started with:
template<typename T, typename S>
void MyTemplateClass< SomeRandomClass<T,S> >::DoSomething(SomeRandomClass<T,S> & t)
{
//something specialized happens here
}
EDIT:
Alternatively, if you only want to keep part of the SomeRandomClass generic, you could:
template<typename T>
void MyTemplateClass< SomeRandomClass<T,int> >::DoSomething(SomeRandomClass<T,int> & t)
{
//something specialized happens here
}
Edit: this is a correct answer to a different question.
Using the typename T twice confuses the issue a little, because they are compiled separately and are not connected in any way.
You can overload the method to take a templated parameter:
template <typename T>
class MyTemplateClass
{
void DoSomething(T& t) { }
template <typename U,typename V>
void DoSomething(SomeRandomClass<<U,V>& r) { }
};
This maps U and V in the new method to T' and S' in SomeRandomClass. In this setup, either U or V could be the same type as T, but they don't have to be. Depending on your compiler, you ought to be able to do
MyTemplateClass<string> mine;
SomeRandomClass<int,double> random;
// note: nevermind the non-const ref on the string literal here...
mine.DoSomething("hello world");
mine.DoSomething(random);
and the templated call will be selected as the matching overload without having to respecify the types explicitly.
Edit:
To do with with template specialization makes no difference to the overload of DoSomething. If you specialize the class as follows
template <>
class SomeRandomClass <int,double>
{
// something here...
};
then the overload above will eat up this specialized implementation gladly. Just be sure the interfaces of the specialized template and the default template match.
If what you're wanting is to specialize DoSomething to take a specific pair of types for SomeRandomClass then you've already lost generality...that's what specialization is.
If you want to use provide a template struct as a template argument (with intent to use it inside) without specializing it:
Here is an example, that appends a type to a tuple given a template sfinae struct as a template argument:
template<typename Tuple, typename T, template<typename> class /*SFINAEPredicate*/>
struct append_if;
template<typename T, template<typename> class SFINAEPredicate, typename ... Types>
struct append_if<std::tuple<Types...>, T, SFINAEPredicate>
{
using type = typename std::conditional<SFINAEPredicate<T>::value,
std::tuple<Types..., T>, std::tuple<Types...>>::type;
};
// usage
using tuple_with_int = append_if<std::tuple<>, int, std::is_fundamental>;
This can be used since C++11.