I've been trying to understand the way C++ selects templates. Namely, consider the following code sample:
template <typename R>
class Curious
{
public:
template <typename T, typename std::enable_if<std::is_const<T>::value, int>::type = 33>
void test1() {}
template <typename T, typename std::enable_if<!std::is_const<T>::value, int>::type = 33>
void test1() {}
template <typename T, typename = typename std::enable_if<std::is_const<T>::value>::type>
void test2() {}
template <typename T, typename = typename std::enable_if<!std::is_const<T>::value>::type>
void test2() {}
template <typename std::enable_if<std::is_const<R>::value>::type * = nullptr>
void test3() {}
template <typename std::enable_if<!std::is_const<R>::value>::type * = nullptr>
void test3() {}
// works
template <typename T = void>
typename std::enable_if<std::is_const<R>::value, T>::type test4() {}
template <typename T = void>
typename std::enable_if<!std::is_const<R>::value, T>::type test4() {}
// also works
template <typename T = void, typename std::enable_if<std::is_const<R>::value, T>::type * = nullptr>
void test5() {}
template <typename T = void, typename std::enable_if<!std::is_const<R>::value, T>::type * = nullptr>
void test5() {}
}; // Curious
The first two functions (test1) work fine (why?):
Curious<int> curious;
curious.test1<int>();
curious.test1<const int>();
While the rest of them cause compilation errors.
Regarding the function test2 the compiler claims I'm trying to create a duplicate:
error C2535: 'void Curious::test2(void)': member function already defined or declared
Here the documentation says:
A common mistake is to declare two function templates that differ only
in their default template arguments. This is illegal because default
template arguments are not part of function template's signature, and
declaring two different function templates with the same signature is
illegal.
So it seems to be the case. However, I don't see that much difference from the first two functions, which also have the default template argument. Thus we have a default type (test2 - doesn't work) against a default value (test1 - works). Is there any rule about it?
In case of test3: error C2039: 'type': is not a member of 'std::enable_if'
Like in the first case this time the member function template has a default non-type parameter, but it depends on the class template parameter. Now SFINAE doesn't skip the wrong one (also not sure why).
In the fourth case SFINAE resolves the template by the return type. But don't these test4 functions have identical signature? As they differ only in the return type.
As far as I understand, in the fifth case adding extra parameter makes test5 signature dependent on the function template parameter, therefore SFINAE kicks in and resolution works.
I'm quite confused about how C++ deals with these templates. Could somebody be so kind to clear these things up?
With default value removed, for test1, you have:
template <typename T, typename std::enable_if<std::is_const<T>::value, int>::type>
void test1();
template <typename T, typename std::enable_if<!std::is_const<T>::value, int>::type>
void test1();
Which have clearly different signatures.
For test2:
template <typename T, typename> void test2();
template <typename T, typename> void test2();
Which are clearly identical signatures.
For test3, SFINAE doesn't apply as you have hard error as R is fixed in the class and your enable_if doesn't depend of template parameter of the function.
For test4, there is an exception about signature for template function as overload may differ only by return type so
int foo();
char foo(); // Illegal.
but
template <typename T> int foo();
template <typename T> char foo(); // legal, even it is not trivial to call
In addition, std::enable_if<!std::is_const<R>::value, T>::type depends on template parameter T so it is ok.
For test5, second template parameter depends on first template parameter T, so it is ok too.
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.
I am trying to do a parameter pack unrolling by dispatching to a class recursively. I'd like to do that from right to left since some of the operations do pre-pending.
template <typename... T>
class Foo;
template <typename T>
class Foo<T> {/* base case implementation*/};
template <typename T, typename R, typename... Rs>
class Foo<T, Rs..., R> {
private:
Foo<T, Rs...> foo_;
}
Unfortunately, the above gets me:
class template partial specialization contains template parameters that cannot be deduced;
this partial specialization will never be used
This seems odd to me, I would assume that even though the arguments has switched order, Foo<T, Rs..., R> should still match the template specialization.
I've looked at some similar questions:
Specifically, C++ template partial specialization: Why cant I match the last type in variadic-template?
However, the highest voted (non-accepted) answer doesn't make sense to me. Sure I understand that the template parameter pack declaration must be the last in the declaration, but I do that for the template specialization.
I'm not sure why the compiler cannot map Foo<T, Rs..., R> to the initial template declaration Foo<T...> and enforces the parameter pack declaration order there.
Other answers on that thread offer how to extract the last value, but that still doesn't allow you to do recursive parameter pack unrolling, which is sort of the whole point here. Is it just impossible to unroll a parameter pack from right to left?
Here is an utility to instatiate a template with a reverse order of template parameters:
#include <type_traits>
#include <tuple>
template <template <typename...> typename Template, typename ...Arg>
struct RevertHelper;
template <template <typename > typename Template, typename Arg>
struct RevertHelper<Template, Arg>
{
using Result = Template<Arg>;
};
template <template <typename... > typename Template, typename Head, typename ...Tail>
struct RevertHelper<Template, Head, Tail...>
{
private:
template <typename ...XArgs>
using BindToTail = Template<XArgs..., Head>;
public:
using Result = typename RevertHelper<BindToTail, Tail...>::Result;
};
static_assert(std::is_same_v<typename RevertHelper<std::tuple, int, double>::Result, std::tuple<double, int>>, "");
So if you need to instantiate Foo with template pack Args... being reversed you can use
typename RevertHelper<Foo, Args...>::Result
To do the parameter pack expansion the way you want, dispatch to the reversed implementation:
namespace internal {
template <typename... T>
class FooHelper;
template <typename T>
class FooHelper<T> {/* base implementation */}
template <typename L, typename R, typename... Rs>
class FooHelper<T> {
private:
Foo<T, Rs...> foo_helper_;
};
}
template <typename... T>
class Foo {
typename RevertHelper<internal::FooHelper, T...>::Result foo_helper_;
};
I'm not sure why the compiler cannot map Foo<T, Rs..., R> to the initial template declaration Foo<T...> and enforces the parameter pack declaration order there.
Because partial ordering is already a really complex algorithm and adding extra complexity to that is fraught with peril. There was a proposal to make this work, which had this example:
template <class A, class... B, class C> void foo(A a, B... b, C c);
foo(1, 2, 3, 4); // b is deduced as [2, 3]
Straightforward enough right? Now, what if C has a default argument? What does this do:
template <class A, class... B, class C=int> void foo(A a, B... b, C c=5);
foo(1, 2, 3, 4);
There are two interpretations of this:
b is deduced as the pack {2, 3} and c is deduced as 4
b is deduced as the pack {2, 3, 4} and c is deduced as 5
Which is intended? Or do we just disallow default arguments after a function parameter pack?
Unfortunately, we have no nice pack indexing mechanism. In the meantime, just use Boost.Mp11:
template <typename... T>
class Foo;
template <typename T>
class Foo<T> {/* base case implementation*/};
template <typename T, typename... Rs>
class Foo<T, Rs...> {
private:
using R = mp_back<Foo>;
mp_pop_back<Foo> foo_;
};
Pattern matching in C++ template patterns is intentionally simplified for sake of simplicity of algorithm and understanding.
Take a look at hypothetical algorithm if this could be possible:
Get some declaration: using X = Foo<int, char, bool, double>;
Compiler checks specializations: first one is Foo - it's dropped.
Compiler checks specializations: second one is your Foo<T, Rs..., R>
T is int, we're fine.
R's can be emtpy, let's try to skip it.
R is char, but we're at the end of specialization parameters, let's get back to 2.
R's is char
R is bool, but we're at the end of specialization parameters, let's get back to 2.
R's is char, bool
R is double, we're fine, select this one
But this is only one scenario: another one would be to eat all parameters to the end and cut off one by one in order to try to match it. This can be problematic, because such template specialization would be inherently ambiguous with another possible specialization that doesn't seem to be an ambiguity here:
template<typename T, typename S>
class Foo<T, S> {};
I'm currently writing a small library of helper templates and have come across an unexpected inconsistency when using std::enable_if with template parameters.
These function templates compile fine in GCC 7.1:
template<typename T, std::enable_if_t<std::is_same_v<int, T>, T>* = nullptr>
void f() { };
template<typename T, std::enable_if_t<std::is_same_v<double, T>, T>* = nullptr>
void f() { };
int main()
{
f<int>();
f<double>();
}
Whereas these struct templates give compilation errors:
template<typename T, std::enable_if_t<std::is_same_v<int, T>, T>* = nullptr>
struct f { };
template<typename T, std::enable_if_t<std::is_same_v<double, T>, T>* = nullptr>
struct f { };
int main()
{
f<int> x;
f<double> y;
}
The errors:
main.cpp:36:70: error: template parameter ‘std::enable_if_t<is_same_v<int, T>, T>* <anonymous>’
template<typename T, std::enable_if_t<std::is_same_v<int, T>, T>* = nullptr>
^~~~~~~
main.cpp:40:12: error: redeclared here as ‘std::enable_if_t<is_same_v<double, T>, T>* <anonymous>’
struct f { };
^
I'm struggling to understand why with the struct the compiler is complaining about:
There being a default value for the second template parameter.
That the struct is re-declared even though the both have different template parameter lists.
This is due to the fact that functions can be overloaded whereas structs cannot.
For functions, both declarations can coexist and enable_if via SFINAE selects the correct overload.
For structs, the template parameters don't matter. Same name => Redeclaration.
Should it be possible to overload function template like this (only on template parameter using enable_if):
template <class T, class = std::enable_if_t<std::is_arithmetic<T>::value>>
void fn(T t)
{
}
template <class T, class = std::enable_if_t<!std::is_arithmetic<T>::value>>
void fn(T t)
{
}
if conditions in enable_if don't overlap? My MSVS compiler complains, that 'void fn(T)' : function template has already been defined. If not, what is the alternative (ideally not putting enable_if anywhere else than into template parameters)?
Default arguments don't play a role in determining uniqueness of functions. So what the compiler sees is that you're defining two functions like:
template <class T, class>
void fn(T t) { }
template <class T, class>
void fn(T t) { }
That's redefining the same function, hence the error. What you can do instead is make the enable_if itself a template non-type parameter:
template <class T, std::enable_if_t<std::is_arithmetic<T>::value, int> = 0>
void fn(T t) { }
template <class T, std::enable_if_t<!std::is_arithmetic<T>::value, int> = 0>
void fn(T t) { }
And now we have different signatures, hence different functions. SFINAE will take care of removing one or the other from the overload set as expected.
Inspired by another question I tried to find a way to deduce the type
of an overload member function given the actual argument used to call
that function. Here is what I have so far:
#include <type_traits>
template<typename F, typename Arg>
struct mem_fun_type {
// perform overload resolution here
typedef decltype(std::declval<F>()(std::declval<Arg>())) result_type;
typedef decltype(static_cast<result_type (F::*)(Arg)>(&F::operator())) type;
};
struct foo {};
struct takes_two
{
void operator()(int);
void operator()(foo);
};
struct take_one {
void operator()(float);
};
int main()
{
static_assert(std::is_same<mem_fun_type<take_one, float>::type,
void (take_one::*)(float)>::value, "Zonk");
static_assert(std::is_same<mem_fun_type<takes_two, double>::type,
void (takes_two::*)(float)>::value, "Zonk");
return 0;
}
As long as the template parameter Arg matches the actual type the
static_cast will succeed, but this is only the most trivial case of
overload resolution (exact match). Is it possible to perform the
complete overload resolution process in template metaprogramming?
This is purely hypothetical and not intended for real-world use.
This is closest I came to it so far: define function returning tables of different sizes and your result is sizeof(select(...)) receiving pointer to function you want to match. To ensure that the code will compile even if function does not exist in given class, you can use separate check has_function.
Result of overload resolution is in select<has_function<T>::value, T>::value.
With this code you can even "resolve" data members, not just functions, it's only a question of making right parameter for select function.
However there is one deficiency here - overload resolution is not on function parameters, but on function type. Meaning none of usual parameter type conversions takes place.
// Verify the name is valid
template <typename T>
struct has_function
{
struct F {int function;};
struct D : T, F {};
template <typename U, U> struct same_;
template <typename C> static char(&select_(same_<int F::*, &C::function>*))[1];
template <typename> static char(&select_(...))[2];
enum {value = sizeof(select_<D>(0)) == 2};
};
// Values to report overload results
enum type { none=1 , function_sz_size_t , function_sz , function_string };
template <bool, typename R> struct select;
template <typename R> struct select<false, R>
{
enum {value = none};
};
template <typename R> struct select<true, R>
{
// Define your overloads here, they don't have to be templates.
template <typename Ret, typename Arg> static char(&select_(Ret (R::*)(const char*, Arg)))[function_sz_size_t];
template <typename Ret, typename Arg> static char(&select_(Ret (R::*)(Arg)))[function_sz];
template <typename Ret> static char(&select_(Ret (R::*)(std::string)))[function_string];
template <typename Ret> static char(&select_(Ret (R::*)(std::string&&)))[function_string];
template <typename Ret> static char(&select_(Ret (R::*)(const std::string&)))[function_string];
static char(&select_(...))[none];
enum {value = sizeof(select_(&R::function))};
};