Concept to check for a nested class template - c++

Suppose I want to be able to write:
template<class T> concept WithNestedTemplate = ...;
struct F { template<class> using nested = int; };
static_assert(WithNestedTemplate<F>);
static_assert(!WithNestedTemplate<int>);
That is, WithNestedTemplate should check for the existence of T::template nested a member class template or alias template with signature template<class> class.
I can write a helper concept:
template<template<class> class> concept TemplateOfᐸclassᐳ = true;
template<class T> concept WithNestedTemplate = TemplateOfᐸclassᐳ<T::template nested>;
But this gives false positives in gcc (the concept erroneously accepts int, e.g.), it's difficult to give the helper concept a sensible name, and now its definition is potentially some way away from where it is used.
Or I can write a generic lambda with tparams:
template<class T> concept WithNestedTemplate = requires {
[]<template<class> class>(){}.template operator()<T::template nested>(); };
But this currently doesn't work in clang (it doesn't like lambdas in unevaluated context), is abominably ugly, and possibly unclear to the reader.
Is there a better way to do this - preferably one that works across all major compilers?
I should probably mention that instantiating T::template nested as the test is not going to work, since it would be legitimate for it to be constrained.

Make a templated struct taking a template <class> class and check that the struct can be instantiated:
template <template <class> class> struct TakesTemplate {};
template<class T> concept WithNestedTemplate = requires {
typename TakesTemplate<T::template nested>;
};
Compiler Explorer link

But this gives false positives in gcc
I think this is because temp.constr.normal#1.4 IIUC:
The normal form of a concept-id C<A1, A2, ..., An> is the normal form of the constraint-expression of C, after substituting A1, A2, ..., An for C's respective template parameters in the parameter mappings in each atomic constraint. If any such substitution results in an invalid type or expression, the program is ill-formed; no diagnostic is required.
But this currently doesn't work in clang (it doesn't like lambdas in unevaluated context)
You can just return true from the lambda and invoke it to form a constant expression:
template<class T> concept WithNestedTemplate =
[]<template<class> class>(){
return true;
}
.template operator()<T::template nested>();
This should be ok because it is an atomic constraint IIUC
To determine if an atomic constraint is satisfied, the parameter mapping and template arguments are first substituted into its expression. If substitution results in an invalid type or expression, the constraint is not satisfied.

Related

Why is the concept in template template argument not verified?

C++20 allows the program to specify concept for template template argument. For example,
#include <concepts>
template <typename T> concept Char = std::same_as<T, char>;
template <typename> struct S {};
template <template <Char U> typename T, typename U> T<U> foo() { return {}; }
int main() { foo<S, int>(); }
the first template argument of the function foo is expected to be a single argument template.
The concept Char is defined to be true only the type char, so an attempt to satisfy it for int shall fail. Still above program is accepted by all compilers: https://gcc.godbolt.org/z/PaeETh6GP
Could you please explain, why the concept in template template argument can be specified, but it will be still ignored?
A template (actual) argument matches a template (formal) parameter if the latter is at least as specialised as the former.
template <Char> typename T is more specialised than template <typename> struct S. Roughly speaking, template <Char> accepts a subset of what template <typename> accepts (the exact definition of what "at least as specialised" actually means is rather involved, but this is a zeroth approximation).
This means that the actual argument can be used in all contexts where the formal parameter can be used. That is, for any type K for which T<K> is valid, S<K> is also valid (because S<K> is valid for any K).
So it is OK to substitute S for T.
If you do it the other way around:
template<typename T> concept Any = true;
template<typename T> concept Char = Any<T> && std::same_as<T, char>;
template<template<Any> class T> void foo();
template<Char> struct S { };
int main()
{
foo<S>();
}
then this is ill-formed, because (roughly) you can say T<int> but not S<int>. So S is not a valid substitute for T.
Notes:
Why would we need this always-true concept Any? What's wrong with simply saying template <template <typename> typename>? Well, that's because of a special rule: if the parameter is not constrained at all, constraints of the argument are ignored, everything goes.
Why write Any<T> && std::same_as<T, char>;? To illustrate a point. The actual rules do not evaluate the boolean values of constraints, but compare constraints as formulae where atomic constraints serve as variables, see here. So the formal reason is that S has a conjunction of a strictly larger (inclusion-wise) set of atomic constraints than T. If S had the same or a strictly smaller set, it would be well-formed. If two sets are not ordered by inclusion, then neither template is more specialised, and there is no match.

Ambiguous partial specializations and enable_if_t

This question is due to insane curiosity rather than an actual problem.
Consider the following code:
template<typename...>
struct type_list {};
template<typename, typename = void>
struct test_class;
template<typename... T>
struct test_class<type_list<T...>> {
static constexpr auto value = false;
};
template<typename T>
struct test_class<type_list<T>, std::enable_if_t<std::is_same_v<T, int>>> {
static constexpr auto value = true;
};
int main() {
static_assert(!test_class<type_list<double, char>>::value);
static_assert(test_class<type_list<int>>::value);
}
This fails with the error:
ambiguous partial specializations of 'test_class<type_list>'
If I changed the second specialization to something that doesn't work from a functional point of view, the error would go away:
template<typename T>
struct test_class<type_list<T>> {
static constexpr auto value = true;
};
Similarly, if I use the alias template void_t, everything works as expected:
template<typename T>
struct test_class<type_list<T>, std::void_t<std::enable_if_t<std::is_same_v<T, int>>>> {
static constexpr auto value = true;
};
Apart from the ugliness of combining void_t and enable_if_t, this also gets the job done when there is a single type that differs from int, ie for a static_assert(!test_class<type_list<char>>::value) (it wouldn't work in the second case instead, for obvious reasons).
I see why the third case works-ish, since the alias template is literally replaced with void when the condition of the enable_if_t is satisfied and type_list<T> is more specialized than type_list<T...> (right?).
However, I would have expected the same also for the following:
template<typename T>
struct test_class<type_list<T>, std::enable_if_t<std::is_same_v<T, int>>> {
static constexpr auto value = true;
};
At the end of the day, std::enable_if_t<std::is_same_v<T, int>> somehow is void when the condition is statisfied (ok, technically speaking it's typename blabla::type, granted but isn't ::type still void?) and therefore I don't see why it results in an ambiguous call. I'm pretty sure I'm missing something obvious here though and I'm curious to understand it now.
I'd be glad if you could point out the standardese for this and let me know if there exists a nicer solution than combining void_t and enable_if_t eventually.
Let's start with an extended version of your code
template<typename, typename = void>
struct test_class;
template<typename T>
struct test_class<type_list<T>> {
static constexpr auto value = false;
};
template<typename... Ts>
struct test_class<type_list<Ts...>> {
static constexpr auto value = false;
};
template<typename T>
struct test_class<type_list<T>, std::enable_if_t<std::is_same_v<T, int>>> {
static constexpr auto value = true;
};
that is called with
test_class<type_list<int>>::value
Try it here!
The standard distinguishes between template parameters that are equivalent, ones that are only functionally equivalent and others which are not equivalent [temp.over.link]/5
Two expressions involving template parameters are considered equivalent if two function definitions containing the expressions would satisfy the one-definition rule, except that the tokens used to name the template parameters may differ as long as a token used to name a template parameter in one expression is replaced by another token that names the same template parameter in the other expression.
Two unevaluated operands that do not involve template parameters are considered equivalent if two function definitions containing the expressions would satisfy the one-definition rule, except that the tokens used to name types and declarations may differ as long as they name the same entities, and the tokens used to form concept-ids may differ as long as the two template-ids are the same ([temp.type]).
Two potentially-evaluated expressions involving template parameters that are not equivalent are functionally equivalent if, for any given set of template arguments, the evaluation of the expression results in the same value.
Two unevaluated operands that are not equivalent are functionally equivalent if, for any given set of template arguments, the expressions perform the same operations in the same order with the same entities.
E.g. std::enable_if_t<std::is_same_v<T, T>> and void are only functionally equivalent: The first term will be evaluated to void for any template argument T. This means according to [temp.over.link]/7 code containing two specialisations <T, void> and <T, std::enable_if_t<std::is_same_v<T, T>> is already ill-formed:
If the validity or meaning of the program depends on whether two constructs are equivalent, and they are functionally equivalent but not equivalent, the program is ill-formed, no diagnostic required.
In the code above std::enable_if_t<std::is_same_v<T, int>> is not even functionally equivalent to any of the other versions as it is in general not equivalent to void.
When now performing partial ordering [temp.func.order] to see which specialisation matches best your call this will result in an ambiguity as test_class is equally specialised [temp.func.order]/6 in both cases (with either Ts={int}, void or T=int, std::enable_if_t<std::is_same_v<T, int>>, both resulting in int, void but you can't deduce them against each other) and therefore the compilation will fail.
On the other hand by wrapping std::enable_if_t with std::void_t, which is nothing more but an alias for void
template <typename T>
using void_t = void;
the partial ordering will succeed as in this case the compiler will already know the type of the last template parameter is void in all cases, choosing test_class<T, std::void_t<std::enable_if_t<std::is_same_v<T,int>>>> with T=int as the most specialised as the non-variadic case (T=int, void) is considered more specialised than the variadic one Ts={int}, void (see e.g. temp.deduct.partial/8 or again this).

Testing if std::pointer_traits can work with my type

How can I check at compile-time whether or not an arbitrary type can be used with std::pointer_traits? I had hoped a simple SFINAE solution might work:
template <typename T, typename = void>
struct pointer_traits_ready : std::false_type {};
template <typename T>
struct pointer_traits_ready<
T,
std::void_t<typename std::pointer_traits<T>::element_type>
> : std::true_type {};
static_assert(!pointer_traits_ready<int>::value,"");
...but this invokes a static assert from within the standard library (ptr_traits.h). Obviously std::is_pointer doesn't accommodate smart pointers.
You can't. From [pointer.traits.types]:
using element_type = see below ;
Type: Ptr::element_type if the qualified-id Ptr::element_type is valid and denotes a type (14.8.2); otherwise, T if Ptr is a class template instantiation of the form SomePointer<T, Args>, where Args is
zero or more type arguments; otherwise, the specialization is ill-formed.
In order to be SFINAE-friendly, we would need pointer_traits<Foo> to simply lack a type alias named element_type. The problem is, element_type is specified as being ill-formed - not absent. So you simply cannot use pointer_traits as a detector for whether or not something can be used as a pointer type.
Even if you wrote your own type that was a SFINAE-friendly version of that specification, you wouldn't be able to catch user specializations of pointer_traits for their own type. Sad panda.

How to avoid the triggering of a static_assert when SFINAE is used?

I'd like to use SFINAE (with void_t) to determine whether a class template specialization or instantiation has a certain member type defined. However the primary class template has a static_assert in it's body.
Is it possible to retrieve this information without modifying the primary template, and without preprocessor tricks?
#include <type_traits>
template <class T>
struct X {
static_assert(sizeof(T) < 0, "");
};
template <>
struct X<int> {
using type = int;
};
template <class...>
using void_t = void;
template <class, class = void_t<>>
struct Has : std::false_type {};
template <class T>
struct Has<T, void_t<typename T::type>> : std::true_type {};
int main() {
static_assert(Has<X<int>>::value == true, "");
// How to make this compile?
static_assert(Has<X<char>>::value == false, ""); // ERROR
}
X<int> is an explicit specialization, though X<char> is an implicit instantiation of the primary template. And when the compiler creates this instantiation then the entire SFINAE machinery is stopped with an error caused by the static_assert declaration inside the primary template body.
My concrete motivation is this: I am creating a generic wrapper class template and I'd like to define a hash function for it if it's template type parameter has a specialization for std::hash<>. However gcc 4.7 puts a static_assert inside the definition of the primary template of std::hash<>. The libstdc++ of gcc 4.8 and llvm's libc++ just simply declare the primary template. Consequently my class template does not work with gcc/libstdc++ 4.7.
// GCC 4.7.2
/// Primary class template hash.
template<typename _Tp>
struct hash : public __hash_base<size_t, _Tp>
{
static_assert(sizeof(_Tp) < 0,
"std::hash is not specialized for this type");
size_t operator()(const _Tp&) const noexcept;
};
// GCC 4.8.2
/// Primary class template hash.
template<typename _Tp>
struct hash;
This problem is similar to this one, but I am not satisfied with the accepted answer there. Because here we cannot "inject 'patches' to to match the static_assert" since the assertion will always fail once the primary template is instantiated with any type parameter.
EDIT: Above I describe my concrete motivation to provide context for the abstract problem which is clearly stated upfront. This concrete motivation refers gcc 4.7, but please try to be independent from the libstdc++ 4.7 implementation details in comments and answers. This is just an example. There can be any kind of C++ libraries out there which might have a primary class template defined similarly to X.
I really don't think so. static_assert at a class level context is meant to make sure you never instantiate a class with a type it cannot handle. It was made to avoid the far away problems created when you instantiate a class with a type that doesn't realize the expected concept but you don't find out until you make a call that uses something expected that's missing...or worse don't and then later it turns up during maintenance.
So static_assert is meant to blow up as soon as you try to instantiate the class, as you are trying to do.
There is one option you might consider though: an external class that can be used to avoid the problem. Basically a traits class that might default to some easy implementation but is overridden for X<T>. This would likely be very fragile though so I don't know that I'd introduce it into something that has a long lifetime without seriously considering other options, like changing the problem class.

How to resolve optional nested type like std::allocator_traits?

An allocator can optionally have nested types like pointer, const_pointer. But one can always use these interface with std::allocator_traits<Allocator>, which would provide a default version of these types if they are absent in Allocator.
How is std::allocator_traits implemented? How can a template choose a default version of nested type when it's absent?
The solution is to refer to the type T::pointer in a context where it does not cause an error if it is not a valid type, instead it causes template argument deduction to fail. The general form of this is known as SFINAE, which stands for "Substitution Failure Is Not An Error". For a explanation of how it works see my SFINAE Functionality Is Not Arcane Esoterica presentation.
There are various techniques, often involving overloaded function templates, but my current favourite uses the void_t idiom to select a partial specialization of a class template:
template<typename T>
using void_t = void;
template<typename T, typename = void>
struct get_pointer
{
using type = typename T::value_type*;
};
template<typename T>
struct get_pointer<T, void_t<typename T::pointer>>
{
using type = typename T::pointer;
};
Now given an allocator type A you can use typename get_pointer<A>::type to refer to A::pointer if that exists, otherwise A::value_type*
The code above works because when A::pointer is a valid type the partial specialization matches and is more specialized than the primary template, and so gets used. When A::pointer is not a valid type the partial specialization is ill-formed, so the primary template is used.