Static_assert before constructor initialization list

There is no templated class that has a template constructor. Is it possible to check a static statement before initializing member variables in such a constructor?

For example, the following code executes T::value() before checking that T has such a method.

 class MyClass { public: template<typename T> MyClass(const T &t) : m_value(t.value()) { static_assert(HasValueMethod<T>::value, "T must have a value() method"); } private: int m_value; }; 

Placing static_assert in the constructor body works fine, except that it prints "T must have a value () method" at the very end, after all error messages from the member initialization list, for example:

 prog.cpp: In instantiation of 'MyClass::MyClass(const T&) [with T = int]': prog.cpp:24:16: required from here prog.cpp:12:21: error: request for member 'value' in 't', which is of non-class type 'const int' : m_value(t.value()) ~~^~~~~ prog.cpp:14:9: error: static assertion failed: T must have a value() method static_assert(HasValueMethod<T>::value, "T must have a value() method"); ^~~~~~~~~~~~~ 

I find this a bit confusing and wonder if it is possible to type “T must have a value () method” before trying to initialize member variables.

I know that I could use enable_if and SFINAE to disable this constructor for unacceptable T s, but I would like to tell the user something more meaningful than the "method not found".

+5
source share
3 answers

You can use std::enable_if for SFINAE from the constructor that executes static_assert , depending on whether T member of the value() function, keeping the actual implementation separate.

The first constructor is selected if T has a value() method and is implemented as usual (except that std::enable_if is required for selection):

 template <typename T, typename = std::enable_if_t<HasValueMethod<T>::value>> MyClass(const T &t) : m_value(t.value()) {} 

So, we need the second constructor, which must be SFINAEd from the function overload, since the first one already knows that T::value exists:

 template <typename T, typename = std::enable_if_t<!HasValueMethod<T>::value>> MyClass(const T &, ...) { static_assert(HasValueMethod<T>::value, "T must have a value() method"); } 

Pay attention to the variable parameter ... : it is necessary in order to differentiate the prototype of the constructor, therefore it does not collide with the first (they must be different, otherwise ambiguous prototypes lead to a compilation error). You don’t give anything to him, it’s just to make him another prototype.

Note also that the predicate for std::enable_if the same, but negated. When HasValueMethod<T>::value is false, the first constructor is SFINAEd from the function overload, but not the second, which then launches a static assert.

You still need to use HasValueMethod<T>::value in the static assert parameter, so it will depend on T Otherwise, if you set only false , it will always start regardless of whether it is selected.

Here GCC prints when T does not have .value() :

 main.cpp: In instantiation of 'MyClass::MyClass(const T&, ...) [with T = A; <template-parameter-1-2> = void]': main.cpp:35:18: required from here main.cpp:21:9: error: static assertion failed: T must have a value() method static_assert(HasValueMethod<T>::value, "T must have a value() method"); ^~~~~~~~~~~~~ 

Here is Clang's:

 main.cpp:21:9: error: static_assert failed "T must have a value() method" static_assert(HasValueMethod<T>::value, "T must have a value() method"); ^ 

In general, there is a problem (as pointed out in the comments of @TC in the comments): MyClass now converted from something in terms of unvalued contexts. I.e

 static_assert(std::is_convertible_v</*anything*/, MyClass>); // Always true. 

In C ++ 20, when, hopefully, concepts exist, this is easily solved with the requires clause:

 template <typename T> requires HasValueMethod<T>::value MyClass(const T &t) : m_value(t.value()) {} 

You can directly express HasValueMethod<T> in the requires clause:

 template <typename T> requires requires (T a) { { a.value() } -> int; } MyClass(const T &t) : m_value(t.value()) {} 

Or converting HasValueMethod<T> to a real concept:

 template <typename T> concept HasValueMethod = requires (T a) { { a.value() } -> int; }; // Inside `class MyClass`. template <typename T> requires HasValueMethod<T> MyClass(const T &t) : m_value(t.value()) {} 

Such solutions make std::is_convertible_v<T, MyClass> work as expected.

+3
source

Bring static_assert() closer to use. In this case, the helper function will do this:

 class MyClass { template<typename T> static int get_value(const T& t) { static_assert(HasValueMethod<T>::value, "T must have a value() method"); return t.value(); } public: template<typename T> MyClass(const T &t) : m_value(get_value(t)) { } private: int m_value; }; 

This not only corrects the order of the error messages, but also allows you to reuse the message for each path that needs the value() member function.

+2
source

if you do not plan on SFINAE-restricting the constructor and you always want the error to occur when HasValueMethod is false, you can simply write a “hard” version of your attribute class:

 template<class T> struct AssertValueMethod { static_assert(HasValueMethod<T>::value, "T must have a value() method"); using type = void; // note: needed to ensure instantiation, see below ... }; template< typename T, typename = typename AssertValueMethod<T>::type > MyClass(const T &t): ... 

In addition, if you later want to add the selected sfinae overload, you can always write a suitable delegation constructor without changing the logic of the static statement ...

+1
source

Source: https://habr.com/ru/post/1273663/


All Articles