I was really excited when I first heard about C++20 constraints and concepts, and so far I've been having a lot of fun testing them out. Recently, I wanted to see if it's possible to use C++20 concepts to test the constraints of classes or functions. For example:
template <int N>
requires (N > 0)
class MyArray { ... };
template <int N>
concept my_array_compiles = requires {
typename MyArray<N>;
};
my_array_compiles<1>; // true
my_array_compiles<0>; // false
At first I didn't have any issues, but I encountered a case where static_assert in a dependent function prevents compilation, even though it appears in a requires expression. Here is an example that illustrates this:
template <bool b>
requires b
struct TestA {
void foo() {}
};
template <bool b>
struct TestB {
static_assert(b);
void foo() {}
};
template <template<bool> class T, bool b>
concept can_foo = requires (T<b> test) {
test.foo();
};
can_foo<TestA, true>; // true
can_foo<TestA, false>; // false
can_foo<TestB, true>; // true
// can_foo<TestB, false>; does not compile
TestA and TestB should work similarly for most use cases (although I found that TestB<false> can be used as a type as long as it isn't instantiated or dereferenced). However, my expectation was that a failed static_assert within a requires expression would cause it to evaluate to false instead. This is especially important for using library code that still uses static_assert. For example, std::tuple_element:
template <class T>
concept has_element_0 = requires {
typename tuple_element_t<0, T>;
};
has_element_0<tuple<int>>; // true
// has_element_0<tuple<>>; does not compile
When I pass in an empty tuple to the above concept, I get the error static_assert failed due to requirement '0UL < sizeof...(_Types)' "tuple_element index out of range". I've tested this on g++ 10.3.0 and clang 12.0.5. I was able to work around this issue by providing a wrapper that uses constraints, but it somewhat defeats the purpose since I am essentially preventing the compiler from seeing the static_assert by enforcing the same condition at a higher level.
template <size_t I, class T>
requires (I >= 0) && (I < tuple_size_v<T>)
using Type = tuple_element_t<I, T>;
template <class T>
concept has_element_0 = requires {
typename Type<0, T>;
};
has_element_0<tuple<int>>; // true
has_element_0<tuple<>>; // false
And it doesn't always work depending on how std::tuple_element is used:
template <size_t I, class T>
requires (I >= 0) && (I < tuple_size_v<T>)
tuple_element_t<I, T> myGet(const T& tup) {
return get<I>(tup);
}
template <class T>
concept has_element_0 = requires (T tup) {
myGet<0>(tup);
};
has_element_0<tuple<int>>; // true
// has_element_0<tuple<>>; does not compile
So ultimately my questions are: is this expected behavior that requires expressions don't take static_assert into account? If so, what was the reason for that design? And finally, is there a better way to accomplish my goal on classes with static_assert without using the above workaround?
Thanks for reading.
Yes, nothing in the content of the stuff you interact with is checked. Just the immediate context of the declaration.
In some cases with decltype the non immediate context of some constructs is checked, but any errors remain hard.
This was done (way back) to reduce the requirements on compilers. Only in what is known as "immediate context" do the compilers need to be able to cleanly back out when they see an error and continue compiling.
Static assert is never suitable for this purpose. Static assert, if hit, ends the compilation.
If you want to avoid the static assert (that is expected to end compilation) then you need to provide an alternative.
Once the concept is designed, create a variant for the not (!) of that concept:
#include <tuple>
#include <variant>
template <std::size_t I, class T>
requires (I >= 0) && (I < std::tuple_size_v<T>)
using Type = std::tuple_element_t<I, T>;
template <class T>
concept has_element_0 = requires {
typename Type<0, T>;
};
bool test1()
{
return has_element_0<std::tuple<int>>; // true
}
bool test2()
{
return has_element_0<std::tuple<>>; // false
}
template <std::size_t I, class T>
requires (I >= 0) && (I < std::tuple_size_v<T>)
std::tuple_element_t<I, T> myGet_impl(const T& tup) {
return get<I>(tup);
}
template <class T>
concept alt_has_element_0 = requires (T tup) {
myGet_impl<0>(tup);
};
template <class T>
auto myGet0();
template <class T>
requires (alt_has_element_0<T>)
auto myGet0(const T& tup)
{
return myGet_impl<0, T>(tup);
}
auto test3()
{
std::tuple<int> X{7};
return myGet0(X); // true
}
template <class T>
requires (!alt_has_element_0<T>)
auto myGet0(const T& tup)
{
return std::monostate{};
}
auto test4()
{
std::tuple<> X;
return myGet0(X); // true
}
see it here;
Notice for test4() to compile, the code above defiles what to do if we do not fulfill the requirements of the concept. I stole std::monostate from variant for this.
Related
This question already has answers here:
How to detect whether there is a specific member variable in class?
(10 answers)
Closed 2 years ago.
I am going down the route
struct S {
static constexpr int extra=5;
};
struct V {
};
template <typename T>
void f()
{
if (std::is_same_v<decltype(T::extra), int>)
std::cout<< "extra exists" <<std::endl;
}
but calling f<S>() fails as
std::is_same_v<decltype(S::extra), int> == 0
and f<V>() does not compile
If you are stuck in c++17, there is some infrastructure that you can add to make detection like this much easier.
Detection-idiom
The most reusable/consistent way to detect features like this is via the Detection idiom, which leverages SFINAE through std::void_t in a template.
This can be taken verbatim from std::experimental::is_detected's page from cppreference. This effectively offers C++17 the ability to detect features in a way that is similiar to C++20's concepts; and the infrastructure can be reused easily for just about any detection.
The basics of what you would need are:
#include <type_traits>
namespace detail {
template <class Default, class AlwaysVoid,
template<class...> class Op, class... Args>
struct detector {
using value_t = std::false_type;
using type = Default;
};
template <class Default, template<class...> class Op, class... Args>
struct detector<Default, std::void_t<Op<Args...>>, Op, Args...> {
using value_t = std::true_type;
using type = Op<Args...>;
};
} // namespace detail
struct nonesuch{};
template <template<class...> class Op, class... Args>
using is_detected = typename detail::detector<nonesuch, void, Op, Args...>::value_t;
Note: The above infrastructure can be reused for any detection. It is a very useful reusable tool in C++17.
With is_detected, all you need is a detector, which is simply a template alias that evaluates to a decltype expression of something that may, or may not, exist.
So in your case, to conditionally detect the presence of T::extra, you can do this with a simple detector like:
template <typename T>
using detect_extra = decltype(T::extra);
Putting it all together now, you can use this detector to conditionally toggle the branch:
if constexpr (is_detected<detect_extra,T>::value) {
// Only do code if 'T' has 'T::extra' (e.g. 'S')
} else {
// Only do code if 'T' does not have 'T::extra' (e.g. 'V')
}
Live Example
If equivalent conversion to a specific type is important, such as extra needing to be convertible to int, you can also use is_detected_convertible and use the detector to check for if the result can be convertible to the desired type. Using the same cppreference page again, you can define is_detected_convertible as:
template <template<class...> class Op, class... Args>
using detected_t = typename detail::detector<nonesuch, void, Op, Args...>::type;
template <class To, template<class...> class Op, class... Args>
using is_detected_convertible = std::is_convertible<detected_t<Op, Args...>, To>;
Which allows the check to instead be:
if constexpr (is_detected_convertible<int, detect_extra, T>::value) {
// Only do code if 'T' has 'T::extra' convertible to int (e.g. 'S')
} else {
// Only do code if 'T' does not have 'T::extra', or is not int
}
Live Example
Concepts (C++20+ only)
If you have access to c++20 and beyond, concepts make this much simpler -- since you can simply use a concept + requires clause like:
#include <concepts> // std::same_as
template <typename T>
concept HasExtra = requires(T) {
{T::extra} -> std::same_as<int>;
};
if constexpr (HasExtra<T>) {
// Only do code if 'T' has 'T::extra' and is 'int' (e.g. 'S')
} else {
// Only do code if 'T' does not have 'T::extra' (e.g. 'V')
}
Live Example
Observe that decltype(T::extra) (when T is S) is int const (is constexpr so is also const), not int. This explain why f<S>() fails.
To test if a class has a member variable there are many ways, I suppose.
A possible solution is develop something as
void type_extra (...);
template <typename T>
auto type_extra (T t) -> decltype(t.extra);
template <typename T>
using type_extra_t = decltype(type_extra(std::declval<T>()));
Now you can write f() as follows
template <typename T>
void f()
{
if ( not std::is_same_v<type_extra_t<T>, void> )
{
std::cout<< "extra exists" <<std::endl;
if ( std::is_same_v<type_extra_t<T>, int> )
std::cout<< "(and is int)" << std::endl;
}
}
Observe that now the type_extra_t<S> is int, not int const; this way (getting the type from the returned type of a function) loose the constness of the variable.
If you want maintain the constness, you can return a reference from the function (so it return a int const & in the S case)
template <typename T>
auto type_extra (T t) -> decltype(t.extra) &;
and remove the reference from the using (so, in the S case, int const & become int const)
template <typename T>
using type_extra_t
= std::remove_reference_t<decltype(type_extra(std::declval<T>()))>;
Edit: This funcion have to check types one by one and return obj of any that will satisfy condition or nullptr.
template <typename... Args, typename = std::enable_if_t<(sizeof...(Args) == 0)>()>
std::nullptr_t f() { return nullptr; }
template <typename T, typename... Args>
BaseClassOfAllArgs* f() {
if (<some condition related to T...>)
return new T;
return f<Args...>();
}
This code works for me. But I wonder if is it possible to rewrite this code to use concept?
I mean something like this:
template <typename... Args>
concept NoArgs = (sizeof...(Args) == 0);
and then use it instead of std::enable_if(this code doesnt work)
template <NoArgs Args>
std::nullptr_t f() { return nullptr; }
template <typename T, typename... Args>
BaseClassOfAllArgs* f() {
if (<some condition related to T...>)
return new T;
return f<Args...>();
}
EDIT: Here is working example of code after taking some tips from
guys in comments. After I added 'Base' to the template it turns out that EmptyPack concept is no longer needed. And First template naturally needs 3 typenames. However, I am not sure about this concept EmptyPack. Is it really making my program ill-formed no diagnostic required?
#include <iostream>
#include <type_traits>
#include <typeinfo>
class X {};
class A : public X {};
class B : public X {};
class C : public X {};
class D : public C {};
class E {};
template<class T, class... Args >
concept DerivedsOfBase = (std::is_base_of_v<T, Args> && ...);
template<typename... Args>
concept EmptyPack = sizeof...(Args) == 0;
template<typename T>
std::nullptr_t f() {
std::cout << "End of the types list" << std::endl;
return nullptr;
}
template<typename Base, typename T, typename... Args> requires DerivedsOfBase<Base, T, Args...>
Base* f() {
std::cout << typeid(T).name() << std::endl;
if (<some condition related to T>)
return new T;
return f<Base, Args...>();
}
int main()
{
auto ptr = f<X, A, B, C>();
auto ptr2 = f<X, A, B, D>();
//auto ptr3 = f<X, A, B, E>(); // compile error
return 0;
}
Any template parameter pack that only has valid instantiations for packs of size 0 makes your program ill-formed, no diagnostic required.
This applies to your first "working" example, as well as any practical variant I can think of using concepts.
From the N3690 draft standard [temp.res] 14.6/8:
If every valid specialization of a variadic template requires an empty template parameter pack, the template is ill-formed, no diagnostic required.
(I've seen that in many versions of C++, I'm just using a random draft standard as it showed up when I googled C++ standard pdf.)
Note that (a) your two f are overloads not specializations, and (b) what C++ programmers mean by "valid specialization of a template" and what the standard means are not quite the same thing.
In essence, pretty much any attempt at your concept will result in ill-formed, no diagnostic required programs.
To see the problem here, we'll reword the standard using negation:
If (every valid specialization of a variadic template requires an empty template parameter pack) then (the template is ill-formed, no diagnostic required).
We can convert this "forall" into a "there exists". "Forall X, P(X)" is the same as "Not( There exists X, Not P(X) )".
Unless (there exists a valid specialization of a variadic template with a non-empty template parameter pack) then (the template is ill-formed, no diagnostic required).
If a variadic template has a requires clause that mandates the variadic template pack is empty, then no valid specialization of that template with an empty parameter pack exists. So your template is ill-formed, no diagnostic required.
In general, these kind of rules exist so that compilers can check if your code would always be nonsense. Things like templates where no type parameter could make it compile, or packs that must be empty, are generally a sign that your code has bugs. By making it ill-formed and not requiring a diagnostic, compilers are permitted to emit diagnostics and fail to compile.
An issue I have with the standard is that it is not just permitted to fail to compile, but it is permitted to compile a program that does literally anything.
Because the compiler is permitted to do this, and some optimizations in other cases actually result in this happening, you should avoid writing code that is ill-formed, no diagnostic required, like the plague.
A workaround is:
namespace implementation_details {
struct never_use_me;
}
template <class=implementation_details::never_use_me>
std::nullptr_t f() { return nullptr; }
template <typename T, typename... Args>
T* f() {
if (<some condition related to T...>)
return new T;
return f<Args...>();
}
another option is:
template <typename T, typename... Args>
T* f() {
if (<some condition related to T...>)
return new T;
if constexpr (sizeof...(Args)==0)
return nullptr;
else
return f<Args...>();
}
You don't really need concepts for that, simply use if constexpr:
T* f() {
if constexpr (sizeof...(Args) != 0) {
if (<some condition related to T...>)
return new T;
return f<Args...>();
} else {
return nullptr;
}
}
If you really want separated functions, you could always use overloading:
template <typename T> // no pack
T* f() {
return nullptr;
}
template <typename T, typename Arg1, typename... Args> // one of more Args
T* f() {
if (<some condition related to T...>)
return new T;
return f<Arg1, Args...>();
}
But of course you can always use requires expressions if you really want to keep the same logic you had with SFINAE. It should be possible (and enough) without a declared concept, but only with constraint:
template <typename T, typename...> // pack logically always empty
T* f() {
return nullptr;
}
template <typename T, typename... Args> requires (sizeof...(Args) > 0)
T* f() {
if (<some condition related to T...>)
return new T;
return f<Args...>();
}
Of course you can also wrap the condition inside a concept, but you gain very little using this technique in this case, since you still have to use the concept inside a require clause:
template<typename... Args>
concept nonempty_pack = sizeof...(Args) > 0;
template <typename T, typename...> // pack logically always empty
T* f() {
return nullptr;
}
template <typename T, typename... Args> requires nonempty_pack<Args...>
T* f() {
if (<some condition related to T...>)
return new T;
return f<Args...>();
}
The syntax template<my_concept T> will send T as the first parameter. You'll always get that parameter snet automatically, hence the need to put the concept in the requires clause.
As you can see in the following example, I currently use a boost::enable_if as return value of an allocation function. The goal is to avoid a compilation error for abstract types:
template <typename T>
typename boost::enable_if<boost::is_abstract<T>,T*>::type no_abstract_new()
{
assert(false);
return 0;
}
template <typename T>
typename boost::disable_if<boost::is_abstract<T>,T*>::type no_abstract_new()
{
return new T;
}
Now, I also want to exclude classes inheriting from a own class named has_no_default_constructor. Is there a way to have a or in the condition of boost::enable_if ? Somthing like this uncorrect code:
template <typename T>
typename boost::enable_if<boost::is_abstract<T>
|| boost::is_base_of<has_no_default_constructor,T>,T*>::type default_constructor_new()
{
assert(false);
return 0;
}
template <typename T>
typename boost::disable_if<boost::is_abstract<T>
|| boost::is_base_of<has_no_default_constructor,T>,T*>::type default_constructor_new()
{
return new T;
}
Or have I to implement a own class of trait doing the job ? (I'm totally lost for this. I understand the idea, but I feel enable to do it myself)
Notes:
I don't use C++11 for compatibility reason
I know has_default_constructor exists in C++11, but not before C++11
boost::has_default_constructor exists but is only an alias of boost::has_trivial_constructor if compiled without C++11
There is also
template <bool B, class T = void> struct enable_if_c;
note that it takes a bool as first parameter not a type. Hence the following should be fine
template <typename T>
typename boost::enable_if_c<boost::is_abstract<T>::value
|| boost::is_base_of<has_no_default_constructor,T>::value
,T*>::type default_constructor_new()
{
assert(false);
return 0;
}
and similar for the other overload.
I was wondering if it was possible for a template (or any other tool you might recommend)to only take a type from a list of specific types (like an enum, but with already existing types).
More specifically, if I have 3 classes, class A, class B and class C, and I'd like a function to be able to take any of those three classes as argument (but no other class than the 3), what should I do ?
Should I use a template (if so how should I use it), or are there other tools for me to use ?
The options that comes to mind for me are SFINAE, static_assert or overloading. What option is best would depend on the function itself.
SFINAE method
#include <type_traits>
template <typename T, typename std::enable_if<
std::is_same<A, T>::value ||
std::is_same<B, T>::value ||
std::is_same<C, T>::value, int>::type = 0>
void foo(T t) {
// ...
}
Static assert
template <typename T>
void foo(T t) {
static_assert(std::is_same<A, T>::value ||
std::is_same<A, T>::value ||
std::is_same<A, T>::value, "Must pass A, B or C");
// ...
}
Overloading
void foo(A a) {
// ...
}
void foo(B a) {
// ...
}
void foo(C a) {
// ...
}
Why do you only want this to work for three classes? In general, it's a better idea to keep your interfaces open and support any types that come along (within reason, of course).
If you're sure you want to do this, then a custom type trait and enable_if is probably the best route.
#include <iostream>
#include <type_traits>
template <typename T>
struct is_my_special_type;
template <>
struct is_my_special_type<int> {
static const bool value = true;
};
template <>
struct is_my_special_type<long> {
static const bool value = true;
};
template <typename T, typename std::enable_if<is_my_special_type<T>::value, int>::type = 0>
void foo(T val) {
std::cout << val << '\n';
}
int main() {
foo(10);
foo(10l);
foo(10.0); // won't compile unless you comment this line out
return 0;
}
You can add types through template specialization of my_special_type. You could also use static_assert instead of enable_if if you wanted.
Constraining template parameters to only be of specific types, or types meeting certain requirements, is part of what C++ Concepts is about.
Concepts are a future feature of the C++ language (coming to the official standard perhaps in C++20), which have been specified in a TS (technical specification) - making them a possible extension for compilers to implement if they wish.
For a short introduction-that's-not-intended-as-such, watch:
What makes a good C++ concept? / Bjarne Stroustrup, CppCon 2016
I'm working on a project which has an template function as so:
template <class T>
T foo<T>(T val) { return someFunc(val); }
template <>
bool foo<bool>(bool val) { return otherFunc(val); };
Now, I have a class Bar, which I don't want to accept as input. In fact, I want it to generate an easy to spot compile error. The problem is that if I do this:
template <>
Bar foo<Bar>(Bar val) { static_assert(false,"uh oh..."); }
It fails on every compile. I found https://stackoverflow.com/a/3926854/7673414, which says that I need to make reference to the template type, otherwise the static assert always takes place. The problem is I don't have a template type here. If I do:
template< typename T >
struct always_false {
enum { value = false };
};
template <>
Bar foo<Bar>(Bar val) { static_assert(always_false<Bar>::value,"uh oh..."); }
then it also always fails compiling. Is there a way to ensure that an instantiation of the template with type Bar always causes a compile error?
Since foo is a complete specialization, it will always get compiled, and the static assert will always get called.
However, there’s an easier way:
template <>
Bar foo<Bar>(Bar val) = delete;
This will say that this specific version is deleted, and cannot be called.
Another way is enable the template (not specialized version) only if the type is different from Bar
template <class T>
typename std::enable_if< ! std::is_same<T, Bar>::value, T>::type foo<T>(T val)
{ return someFunc(val); }
If you can use C++14, is can be simplified using std::enable_if_t
template <class T>
std::enable_if_t< ! std::is_same<T, Bar>::value, T> foo<T>(T val)
{ return someFunc(val); }
You can use std::is_same to help with your requirement.
template <class T>
T foo<T>(T val)
{
// Make sure T is not Bar
static_assert(std::is_same<T, Bar>::value == false, "uh oh...");
return someFunc(val);
}
If you are using c++17, you can put everything together with constexpr if:
template< typename T >
auto foo( T val )
{
static_assert( !std::is_same_v<T,Bar> );
if constexpr( std::is_same_v<T,bool> )
{
return other_func( val );
}
else
{
return some_func( val );
}
}
Then you can static_assert at the first line, without the pain of compilation failure of the template specific instantiation.
A live example is available at https://wandbox.org/permlink/PpR6G0gcvMRoxhhZ