concept in header doesn't constrain a parameter pack in C++20? - c++

Context: I am rewriting a library that worked with the GCC -fconcepts to C++20. Clang 10 and GCC 10 give me the same unexpected problem, so it's probably my fault.
I have a class template that supports two cases. It can either be created from a list of pin_out's, or from a list of port_out's.
template< typename T > concept pin_out = T::is_pin_out;
template< typename... Ts > concept pin_out_list = ( pin_out< Ts > && ... );
template< typename T > concept port_out = T::is_port_out;
template< typename... Ts >
requires pin_out_list< Ts...> || ( port_out< Ts > && ... )
struct port;
When I write the specialization for a list of pins_out's, with the concepts TS I could write
template< pin_out_list... Ts >
struct port< Ts... > {};
but now with C++20 the compilers complain that the specialization is not more constrained than the base. When I add a requires clause it does compile.
template< pin_out_list... Ts >
requires pin_out_list< Ts... >
struct port< Ts... > {};
And I can remove the pin_out_list... from the template header.
template< typename... Ts >
requires pin_out_list< Ts... >
struct port< Ts... > {};
Is the pin_out_list... in the specialization now silently ignored?
test it on compiler explorer

One of the many things that P1141 changed was what a variadic constraint actually means:
In [temp.param]/11 we have:
template <C2... T> struct s3; // associates C2<T...>
This seems to be doing an unexpected thing, which is having the constraint apply to more than one type in a pack at a time.
And as a result of that paper, a variadic constraint like that now applies to every type in the back. That is, we now have (this is in [temp.param]/5 now):
template <C2... T> struct s3; // associates (C2<T> && ... )
As a result, this specialization:
template< pin_out_list... Ts >
struct port< Ts... > {};
means:
template <typename... Ts> requires (pin_out_list<Ts> && ...)
struct port<Ts...>;
and not:
template <typename... Ts> requires pin_out_list<Ts...>
struct port<Ts...>;
You need the latter meaning (this is the constraint in the primary expression) so you need to write the latter syntax. The compiler wasn't silently ignoring your specialization.

Related

C++ Take a union of the parameters in a parameter pack of variadic templates?

I've been writing a bit of code in C++ that involves a lot of metaprogramming; the code models a task that is defined at runtime by some series of nodes that take a number of inputs and, when run, produce some number of outputs. Inputs and outputs are linked in a directed graph. There's a bounded number of node types, which I'm storing in a variant, and each node class defines a static constexpr variable that, more or less, lists what types are input and output from that node. I'd like to be able to take a list of the classes of nodes and convert it into a list of the classes of data that are acted upon by the nodes.
To be more explicit, if I have a template that purely tracks some list of classes at compile-time like this:
template<class... Args>
struct ClassList{};
I want some sort of template that transforms list of lists like this:
ClassList<ClassList<int,int,double>, ClassList<double,char>, ClassList<char, char> >
into the union of the inner lists:
ClassList<int,double,char>
with no special requirements for order - except that each type appearing in the original lists appears exactly once in the final list - and if possible, I'd like to do this in a way that won't cause my compiler to explode. I know that I could, in theory, write a whole bunch of recursive templates that would join the lists into one and then remove duplicates, but that solution sounds a bit unsavory. Is there a better way?
A solution that filters types before adding them into the final list:
template<class... Ts>
struct class_list {};
template<class... Ts>
struct make_unique {
using type = class_list<Ts...>;
};
template<class... Ts>
struct make_unique<class_list<>, Ts...> : make_unique<Ts...>{};
template<class U, class... Us, class... Ts>
struct make_unique<class_list<U, Us...>, Ts...>
: std::conditional_t<
(std::is_same_v<U, Us> || ...) || (std::is_same_v<U, Ts> || ...),
make_unique<class_list<Us...>, Ts... >,
make_unique<class_list<Us...>, Ts..., U>> {};
template<class... Ts>
using make_unique_class_list = typename make_unique<Ts...>::type;
using T = make_unique_class_list<class_list<int, int, double>,
class_list<double, char>, class_list<char, char>>;
static_assert(std::is_same_v<T, class_list<int, double, char>>);
Note that the following similarly looking solution also works but can be too slow, because it instantiates many unnecessary templates:
template<class U, class... Us, class... Ts>
struct make_unique<class_list<U, Us...>, Ts...>
: std::conditional<
(std::is_same_v<U, Us> || ...) || (std::is_same_v<U, Ts> || ...),
typename make_unique<class_list<Us...>, Ts... >::type,
typename make_unique<class_list<Us...>, Ts..., U>::type> {};

SFINAE inside std::enable_if argument

I have different view types, which each have a std::size_t View::dimension member constant, and a typename View::value_type member type.
The following compile-type check should verify if both From and To are views (verified using is_view<>), and the content of From can be assigned to To. (same dimensions, and convertible value types).
template<typename From, typename To>
struct is_compatible_view : std::integral_constant<bool,
is_view<From>::value &&
is_view<To>::value &&
From::dimension == To::dimension &&
std::is_convertible<typename From::value_type, typename To::value_type>::value
> { };
is_view<T> is such that it always evaluates to std::true_type or std::false_type, for any type T. The problem is that if From or To is not a view type, then From::dimension (for example) may not exist, and is_compatible_view<From, To> causes a compilation error.
It should instead evaluate to std::false_type in this case.
is_compatible_view is used for SFINAE with std::enable_if, to disable member functions. For example a view class can have a member function
struct View {
constexpr static std::size_t dimension = ...
using value_type = ...
template<typename Other_view>
std::enable_if_t<is_compatible_view<Other_view, View>> assign_from(const Other_view&);
void assign_from(const Not_a_view&);
};
Not_a_view is not a view, and causes a compilation error in is_compatible_view<Not_a_view, ...>. When calling view.assign_from(Not_a_view()), SFINAE does not apply, and instead a compilation error occurs when the compiler tries to resolve the first assign_from function.
How can is_compatible_view be written such that this works correctly? In C++17 does std::conjunction<...> allow this?
One approach is using something like std::conditional to delay evaluation of some parts of your type trait until we've verified that other parts of your type trait are already true.
That is:
// this one is only valid if From and To are views
template <class From, class To>
struct is_compatible_view_details : std::integral_constant<bool,
From::dimension == To::dimension &&
std::is_convertible<typename From::value_type, typename To::value_type>::value
> { };
// this is the top level one
template<typename From, typename To>
struct is_compatible_view : std::conditional_t<
is_view<From>::value && is_view<To>::value,
is_compatible_view_details<From, To>,
std::false_type>::type
{ };
Note that I'm using both conditional_t and ::type. is_compatible_view_details will only be instantiated if both From and To are views.
A similar approach would be to use std::conjunction with the above, which because of short-circuiting will similarly delay evaluation:
template <class From, class To>
struct is_compatible_view : std::conjunction_t<
is_view<From>,
is_view<To>,
is_compatible_view_details<From, To>
>
{ };
Either way, you need to pull out the details.
A third approach would be to use enable_if_t as a specialization:
template <class From, class To, class = void>
struct is_compatible_view : std::false_type { };
template <class From, class To>
struct is_compatible_view<From, To, std::enable_if_t<
is_view<From>::value &&
is_view<To>::value &&
From::dimension == To::dimension &&
std::is_convertible<typename From::value_type, typename To::value_type>::value>>
: std::true_type { };
Here, if any of the expressions in the enable_if_t are ill-formed, SFINAE kicks in and we just use the primary template, which is false_type.

is it possible to write a generalized rebind template?

without specializing for each class template/class, is it possible to write a generalized 'rebind' meta function, so that
given
template<class > struct foo;
struct bar;
the following
is_same<rebind<foo<int>,float>,foo<float>>
is_same<rebind<bar>,bar>
and maybe
is_same< rebind<std::vector<int>,float>,std::vector<float>>
returns a type equivalent of true?
Sure.
But be aware that any template template parameter taking a variadic template parameter list is restricted to accepting templates with only type parameters, not non-type parameters. In other words, the general case below won't work for std::array because its second argument is an integer. You'd have to add a special case.
The primary template is already a special case, since it handles classes that aren't a specialization of a template.
http://liveworkspace.org/code/5b6f0cb3aec1eb74701e73d8d21aebab
template< typename bound, typename ... new_args >
struct rebind_class {
static_assert( sizeof ...( new_args ) == 0,
"can't rebind arguments to non-specialization" );
typedef bound type;
};
template< template< typename ... > class template_, typename ... new_args,
typename ... old_args >
struct rebind_class< template_< old_args ... >, new_args ... > {
typedef template_< new_args ... > type;
};
template< typename ... args >
using rebind = typename rebind_class< args ... >::type;

Partial template specialization with multiple template parameter packs

Continuing my journey into the world of variadic templates, I encountered another problem.
Assuming the following template class:
template < typename T >
struct foo
{
//default implementation
};
it is possible to partially specialize it for variadic template instantiations like this:
template < template < typename ... > class T, typename ...Args >
struct foo< T< Args... > >
{
//specialized implementation
};
With this, foo< int > will correspond to the default implementation and foo< std::tuple< int, char > > to the specialized implementation.
However, things become more complicated when using several template parameters. For example, if we have the following template class
template < typename T, typename U >
struct bar {};
and we want to partially specialize it as we did for foo, we cannot do
template < template < typename ... > class T, typename ...TArgs,
template < typename ... > class U, typename ...UArgs >
struct bar< T< TArgs... >, U< UArgs... > > {};
//This would correspond to the specialized version with
//T=std::tuple,
//TArgs=int,char
//U=std::tuple,
//UArgs=float
bar< std::tuple< int, char >, std::tuple< float > > b;
Indeed, if I am correct, we can only have one template parameter pack and it must be positioned at the end of the parameter list. I understand why this is mandatory in template declarations, but for certain partial template specialization (like the example above), this should not be an issue.
Is it possible to achieve partial template specialization with multiple template parameter packs?
Edit: Now I feel silly...the code I gave above compiles perfectly (at least with gcc 4.5). The compile error I had was not because of multiple parameter packs, but because of their use as member functions parameters. In the partial specialization of bar, I tried to define a member function that takes both TArgs and UArgs parameters:
template < template < typename ... > class T, typename ...TArgs,
template < typename ... > class U, typename ...UArgs >
struct bar< T< TArgs... >, U< UArgs... > >
{
void method( TArgs... targs, UArgs... uargs ) //compile error here
{
}
};
On the member function declaration, gcc gives me the error
parameters packs must be at the end of the parameter list.
As far as I can tell, the compiler should be able to define the correct member function for a given template instantiation, e.g. bar< std::tuple< int, char >, std::tuple< float > > should contain a member function void method( int, char, float ). Am I doing something wrong? Or am I trying to do something that is not possible? If so, is there a good reason why this is not possible?
Probably this answer won't clear your question directly,
but the following code compiled on ideone(gcc-4.5.1) when I tested.
#include <cstdio>
#include <tuple>
template< class, class > struct S {
S() { puts("primary"); }
};
template<
template< class... > class T, class...TArgs
, template< class... > class U, class...UArgs
>
struct S< T< TArgs... >, U< UArgs... > > {
S() { puts("specialized"); }
};
int main()
{
S< int, int > p; // "primary"
S< std::tuple< int, char >, std::tuple< float > > s; // "specialised"
}
I'm not sure this code is strictly conformant, but
as far as I read N3225 14.5.3, I couldn't find the statement which mentions
that template parameter pack has to be the last template parameter.
Edit:
I reread N3225 and found the following statements:
8.3.5/4 If the parameter-declaration-clause
terminates with an ellipsis or a
function parameter pack (14.5.3), the
number of arguments shall be equal to
or greater than the number of
parameters that do not have a default
argument and are not function
parameter packs.
14.8.2.5/10 [Note: A function parameter pack can only occur at the
end of a
parameter-declarationlist(8.3.5). -end
note]
So, as you mentioned, function parameter pack has to be the last parameter
unfortunately.
A non-template member function of a class template is an ordinary function
for that class when it is instantiated(fully specialized).
So I wish that the code in this question can be compiled logically, as a
special case.

Check if parameter pack contains a type

I was wondering if C++0x provides any built-in capabilities to check if a parameter pack of a variadic template contains a specific type. Today, boost:::mpl::contains can be used to accomplish this if you are using boost::mpl::vector as a substitute for variadic templates proper. However, it has serious compilation-time overhead. I suppose, C++0x has compiler-level support for std::is_same. So I was thinking if a generalization like below is also supported in the compiler.
template <typename... Args, typename What>
struct is_present
{
enum { value = (What in Args...)? 1 : 0 };
};
Fortunately, the C++ standard has evolved. With C++1z aka C++17, you can finally iterate easily over parameter packs. So the code for the answer is (almost) as simple, as suggested in the question:
template<typename What, typename ... Args>
struct is_present {
static constexpr bool value {(std::is_same_v<What, Args> || ...)};
};
The weird-looking (std::is_same_v<What, Args> || ...) is expanded by the compiler internally to (std::is_same_v<What, Args[0]> || std::is_same_v<What, Args[1]> || ...), which is exactly, what you want. It even correctly yields false with an empty Args parameter pack.
It is even possible to do the whole check inline in a function or method - no helper structs are required anymore:
template<typename T, typename ... List>
void foo(T t, List ... lst)
{
if constexpr((std::is_same_v<T, List> || ...)) {
std::cout << "T is in List" << std::endl;
} else {
std::cout << "T is not in List" << std::endl;
}
}
Note: This has been taken from another question, that was marked as a duplicate of this question. As this is the "canonical" question for this topic, I added that important information here.
No, you have to use (partial) specialization with variadic templates to do compile-time computations like this:
#include <type_traits>
template < typename Tp, typename... List >
struct contains : std::true_type {};
template < typename Tp, typename Head, typename... Rest >
struct contains<Tp, Head, Rest...>
: std::conditional< std::is_same<Tp, Head>::value,
std::true_type,
contains<Tp, Rest...>
>::type {};
template < typename Tp >
struct contains<Tp> : std::false_type {};
There is only one other intrinsic operation for variadic templates and that is the special form of the sizeof operator which computes the length of the parameter list e.g.:
template < typename... Types >
struct typelist_len
{
const static size_t value = sizeof...(Types);
};
Where are you getting "it has serious compilation-time overhead" with boost mpl from? I hope you are not just making assumptions here. Boost mpl uses techniques such as lazy template instantiation to try and reduce compile-times instead of exploding like naive template meta-programming does.
If you want to avoid manual type recursion, std::common_type appears to me to be the only utility in the STL which is a variadic template, and hence the only one which could potentially encapsulate recursion.
Solution 1
std::common_type finds the least-derived type in a set of types. If we identify numbers with types, specifically high numbers with less-derived types, it finds the greatest number in a set. Then, we have to map equality to the key type onto a level of derivation.
using namespace std;
struct base_one { enum { value = 1 }; };
struct derived_zero : base_one { enum { value = 0 }; };
template< typename A, typename B >
struct type_equal {
typedef derived_zero type;
};
template< typename A >
struct type_equal< A, A > {
typedef base_one type;
};
template< typename Key, typename ... Types >
struct pack_any {
enum { value =
common_type< typename type_equal< Key, Types >::type ... >::type::value };
};
Solution 2
We can hack common_type a little more. The standard says
A program may specialize this trait if
at least one template parameter in the
specialization is a user-defined type.
and describes exactly what is inside it: a recursive partial specialization case, a case which applies a binary operator, and a terminal case. Essentially, it's a generic fold function, and you can add whatever binary operation you please. Here I used addition because it's more informative than OR. Note that is_same returns an integral_constant.
template< typename Addend >
struct type_sum { // need to define a dummy type to turn common_type into a sum
typedef Addend type;
};
namespace std { // allowed to specialize this particular template
template< typename LHS, typename RHS >
struct common_type< type_sum< LHS >, type_sum< RHS > > {
typedef type_sum< integral_constant< int,
LHS::type::value + RHS::type::value > > type; // <= addition here
};
}
template< typename Key, typename ... Types >
struct pack_count : integral_constant< int,
common_type< type_sum< is_same< Key, Types > > ... >::type::type::value > {};