Template template functions and parameters deduction - c++

I've got a problem with template templates and parameters deduction. Here's the code:
template<typename U, template<typename> class T>
void test(T<U>&& t)
{
...
}
I expected this to accept either lvalues and rvalues, but only works with rvalues. The collapsing rule "T& && = T&" doesn't apply in this case?
Naturally I could declare the lvalue reference function too, but makes the code less readable.
If you're asking why I need this is to use a static_assert to check if T is a particular class. If there's a simpler way to do so I'll be happy to change my code, but I'd like to know if template templates are usable in this way.
Thanks

Unlike typename T, which can be deduced to be a reference type, template<typename> class T can only ever be deduced to be a class template, so T<U> is always deduced to an object type.
You can write your function templated on T then unpack the template type in the static_assert:
template<typename T> struct is_particular_class: std::false_type {};
template<typename U> struct is_particular_class<ParticularClass<U>>: std::true_type {};
template<typename T> void test(T &&) {
static_assert(is_particular_class<std::remove_reference<T>::type>::value, "!");
}

Related

When do we need Tag Dispatching in Template Metaprogramming?

I am new to template metaprogramming and I was watching the type traits talk part II by Jody Hagins. I wanted to replicate a function overload resolution example to detect whether a given type is constant using the following:
namespace detail {
template <typename T>
std::true_type is_const(T const);
template <typename T>
std::false_type is_const(T);
} // namespace detail
template <typename T>
using is_constant = decltype(detail::is_const(std::declval<T>()));
static_assert(is_constant<int const>::value);
The above static assertion produces a compiler error saying call to is_const is ambiguous. If I use a TypeTag to enclose T in my template declarations, things work as expected:
template <typename T>
struct TypeTag {};
namespace detail {
template <typename T>
std::true_type is_const(TypeTag<T const>);
template <typename T>
std::false_type is_const(TypeTag<T>);
} // namespace detail
template <typename T>
using is_constant = decltype(detail::is_const(std::declval<TypeTag<T>>()));
static_assert(is_constant<int const>::value);
I am confused as to why the first declarations without TypeTag encapsulation are ambiguous. My guess is it has something to do with declval return type being T for cv-qualified types but then I do not understand how the second case works.
Is it because in the first case declval<int const> has return type int but in the second case declval<TypeTag<int const>> has return type TypeTag<int const> so the compiler picks the first template specialization where T is replaced with int and the template invocation looks like this:
<>
std::true_type is_const<TypeTag<int const>>;
If my guess is correct, is there a general practice to use tag dispatching with a TypeTag (empty struct template) to guard against cv-qualified types?
The reason the 1st example does not work is that top-level const in a function argument is ignored, so is_const(T) and is_const(T const) are the same function signatures. If const is not top-level, function signatures are different, e.g. is_const(T*) and is_const(T* const) are different.
In your 2nd example, is_const(TypeTag<T>) and is_const(TypeTag<T const>) are different because TypeTag<T> and TypeTag<T const> are unrelated types.
However, I don't think that your use of TypeTag qualifies as "tag dispatch".

Check if class is a template specialization

I want to check if a class is a template specialization of another one. What I have tried is:
template <class T, template <class...> class Template>
struct is_specialization : std::false_type {};
template <template <class...> class Template, class... Args>
struct is_specialization<Template<Args...>, Template> : std::true_type {};
It works fine when all template parameters are type arguments but not when some are non-type arguments. For example it works with std::vector but not std::array (since the later accepts an non-type argument std::size_t).
It's important that the check is made at compile time. Also the solution must work for any template, not just vectors or arrays. That means that it can be any number of type arguments and any number of non-type arguments. For example it should work with template <class A, bool B, class C, int D, class... Args> class foo;
C++20 is a weird, weird world. Cross-checking is welcome as I'm a beginner with CTAD and not entirely sure I've covered all bases.
This solution uses SFINAE to check whether class template argument deduction (CTAD) succeeds between the requested class template and the mystery type. An additional is_same check is performed to prevent against unwanted conversions.
template <auto f>
struct is_specialization_of {
private:
template <class T>
static auto value_impl(int) -> std::is_same<T, decltype(f.template operator()<T>())>;
template <class T>
static auto value_impl(...) -> std::false_type;
public:
template <class T>
static constexpr bool value = decltype(value_impl<T>(0))::value;
};
// To replace std::declval which yields T&&
template <class T>
T declrval();
#define is_specialization_of(...) \
is_specialization_of<[]<class T>() -> decltype(__VA_ARGS__(declrval<T>())) { }>::value
// Usage
static_assert(is_specialization_of(std::array)<std::array<int, 4>>);
First caveat: Since we can't declare a parameter for the class template in any way without knowing its arguments, passing it around to where CTAD will be performed can only be done by jumping through some hoops. C++20 constexpr and template-friendly lambdas help a lot here, but the syntax is a mouthful, hence the helper macro.
Second caveat: this only works with movable types, as CTAD only works on object declarations, not reference declarations. Maybe a future proposal will allow things such as std::array &arr = t;, and then this will be fixed!
Actually fixed by remembering that C++17 has guaranteed copy-elision, which allows direct-initialization from a non-movable rvalue as is the case here!

universal reference for template type error

Excuse the bad title...
So, normally in a function like this:
template <class T>
void f(T&& i){
}
T&& is a universal reference. In a context like this it is an rvalue-reference:
template <class T>
struct s{
void f(T&& t){}
};
This makes sense, as T is a fixed type relative to s of which f() is a method.
However, apparently in this context:
template <class T, unsigned n>
std::array<T, n>
f(T&&){
}
T&& is an rvalue-reference as well. This case is different from the above one though. So, what is the reason for T&& being an rvalue-reference here as well rather than a universal one?
That's because you supplied the parameter type explicitly (I am assuming this much, but this is the only way to make your example compile).
f<int,5> performs no type deductions at all, and its parameter is int&&. There is no reference collapsing going on.
You could work around this by writing the f template as such:
template<unsigned n, typename T>
array<decay_t<T>, n>
f(T&& t);
Now, t is a forwarding/universal reference if you don't provide the second template argument explicitly.

Universal reference with templated class

Example:
template <typename T>
class Bar
{
public:
void foo(T&& arg)
{
std::forward<T>(arg);
}
};
Bar<int> bar;
bar.foo(10); // works
int a{ 10 };
bar.foo(a); // error C2664: cannot convert argument 1 from 'int' to 'int &&'
It seems that universal references works only with templated functions and only with type deduction, right? So it make no sense to use it with class? And does using of std::forward makes sense in my case?
Note that the preferred terminology (i.e. the one which will be in future versions of the spec) is now forwarding reference.
As you say, a forwarding reference only works with type deduction in a function template. In your case, when you say T&&, T is int. It can't be int& because it has been explicitly stated in your Bar instantiation. As such, reference-collapsing rules can't occur, so you can't do perfect forwarding.
If you want to do perfect forwarding in a member function like that, you need to have a member function template:
template <typename U>
void foo(U&& arg)
{
std::forward<U>(arg); //actually do something here
}
If you absolutely need U to have the same unqualified type as T, you can do a static_assert:
template <typename U>
void foo(U&& arg)
{
static_assert(std::is_same<std::decay_t<U>,std::decay_t<T>>::value,
"U must be the same as T");
std::forward<U>(arg); //actually do something here
}
std::decay might be a bit too aggressive for you as it will decay array types to pointers. If that's not what you want, you could write your own simple trait:
template <typename T>
using remove_cv_ref = std::remove_cv_t<std::remove_reference_t<T>>;
template <typename T, typename U>
using is_equiv = std::is_same<remove_cv_ref<T>, remove_cv_ref<U>>;
If you need a variadic version, we can write an are_equiv trait. First we need a trait to check if all traits in a pack are true. I'll use the bool_pack method:
namespace detail
{
template<bool...> struct bool_pack;
template<bool... bs>
using all_true = std::is_same<bool_pack<bs..., true>, bool_pack<true, bs...>>;
}
template <typename... Ts>
using all_true = detail::all_true<Ts::value...>;
Then we need something to check if each pair of types in Ts... and Us... satisfy is_equiv. We can't take two parameter packs as template arguments, so I'll use std::tuple to separate them (you could use a sentinel node, or split the pack halfway through instead if you wanted):
template <typename TTuple, typename UTuple>
struct are_equiv;
template <typename... Ts, typename... Us>
struct are_equiv <std::tuple<Ts...>, std::tuple<Us...>> : all_true<is_equiv<Ts,Us>...>
{};
Then we can use this like:
static_assert(are_equiv<std::tuple<Ts...>,std::tuple<Us...>>::value,
"Us must be equivalent to Ts");
You're right : "universal references" only appear when the type of a deduced parameter is T&&. In your case, there is no deduction (T is known from the class), hence no universal reference.
In your snippet, std::forward will always perform std::move as arg is a regular rvalue reference.
If you wish to generate a universal reference, you need to make foo a function template :
template <typename T>
class Bar
{
public:
template <typename U>
void foo(U&& arg)
{
std::forward<U>(arg);
}
};

Understanding SFINAE

As far as I know, SFINAE means substitution failures do not result in compilation errors, but just remove the prototype from the list of possible overloads.
What I do not understand: why is this SFINAE:
template <bool C, typename T = void> struct enable_if{};
template <typename T> struct enable_if<true, T> { typedef T type; };
But this is not?
template <bool C> struct assert;
template <> struct assert<true>{};
From my understanding, the underlying logic is identical here. This question emerged from the comments to this answer.
In C++98, SFINAE is done with either a return type or a function's dummy argument with default parameter
// SFINAE on return type for functions with fixed arguments (e.g. operator overloading)
template<class T>
typename std::enable_if< std::is_integral<T>::value, void>::type
my_function(T const&);
// SFINAE on dummy argument with default parameter for functions with no return type (e.g. constructors)
template<class T>
void my_function(T const&, std::enable_if< std::is_integral<T>::value, void>::type* = nullptr);
In both cases, substution of T in order to get the nested type type is the essence of SFINAE. In contrast to std::enable_if, your assert template does not have a nested type that can be used in substitution part of SFINAE.
See Jonathan Wakely's excellent ACCU 2013 presentation for more details and also for the C++11 expression SFINAE. Among others (as pointed out by #BartekBanachewicz in the comments) is is now also possible to use SFINAE in function template default arguments
// use C++11 default function arguments, no clutter in function's signature!
template<class T, class dummy = typename std::enable_if< std::is_integral<T>::value, void>::type>
void my_function(T const&);