Typical attribute for aggregate initialization in a standard library?

The C ++ standard library has std::is_constructible<Class, T...> to check if a class can be created from given types as arguments.

For example, if I have a class MyClass that has a constructor MyClass(int, char) , then std::is_constructible<MyClass, int, char>::value will be true .

Is there a similar type of standard library type that will verify that the aggregate is initializing, i.e. MyClass{int, char} is correct and returns MyClass ?

My use case:

I want to write a function template that converts the std::tuple class to a class (usually POD) using aggregate initialization, something with the following signature:

 template <typename Class, typename... T> inline Class to_struct(std::tuple<T...>&& tp); 

To prevent users from using this function with an invalid Class , I could write static_assert inside this function to check if the given tp parameter has types that can be converted to Class members. It seems like a type like is_aggregate_initializable<Class, T...> , come in handy.

I could implement my own implementation of this trait, but only for information, is there such a trait in the standard library that I missed, or one that will soon become part of the standard library?

+5
source share
1 answer

From a discussion of comments and viewing a link to C ++, it seems that there is a standard feature like library for neither aggregate initialization nor list initialization , at least until C ++ 17.

The comments emphasized that there is a difference between list parameterization ( Class{arg1, arg2, ...} ) and aggregate initialization .

Initialization of a list (in particular, straightforward initialization of a list) is easier to write a type trait, because this trait depends solely on the validity of the specific syntax. For my use case for testing, if the structure can be built from tuple elements, it seems that initializing the source list is more appropriate.

A possible way to implement this feature (with the appropriate SFINAE) is as follows:

 namespace detail { template <typename Struct, typename = void, typename... T> struct is_direct_list_initializable_impl : std::false_type {}; template <typename Struct, typename... T> struct is_direct_list_initializable_impl<Struct, std::void_t<decltype(Struct{ std::declval<T>()... })>, T...> : std::true_type {}; } template <typename Struct, typename... T> using is_direct_list_initializable = detail::is_direct_list_initializable_impl<Struct, void, T...>; template<typename Struct, typename... T> constexpr bool is_direct_list_initializable_v = is_direct_list_initializable<Struct, T...>::value; 

Then we can check the direct list initialization by doing is_direct_list_initializable_v<Class, T...> .

This also works with move semantics and perfect forwarding, because std::declval follows the excellent forwarding rules.

Aggregate initialization is less simple, but there is a solution that covers most cases. Aggregate initialization requires the type to be initialized as an aggregate (see Explanation in C ++ reference for aggregate initialization ), and we have C ++ 17 trait std::is_aggregate , which checks if the type is aggregated.

However, this does not mean that just because the type is an aggregate, normal direct list initialization is not valid. Initialization of the standard list, which corresponds to the constructors, is still allowed. For example, the following compilations:

 struct point { int x,y; }; int main() { point e1{8}; // aggregate initialization :) point e2{e1}; // this is not aggregate initialization! } 

To prohibit this type of list initialization, we can use the fact that aggregates cannot have custom (i.e., user-provided) constructors, so non-aggregate initialization should have only one parameter and Class{arg} will satisfy std::is_same_v<Class, std::decay_t<decltype(arg)>> .

Fortunately, we cannot have a member variable of the same type as the wrapper class , so the following is not valid:

 struct point { point x; }; 

There is a caveat: reference types to the same object are allowed, because member references can be incomplete (GCC, Clang and MSVC all accept this without any warning):

 struct point { point& x; }; 

While unusual, this code is valid by standard . I have no solution to detect this case and determine that point is an aggregate initialized by an object of type point& .

Ignoring the warning above (you rarely need to use this type), we can develop a solution that will work:

 template <typename Struct, typename... T> using is_aggregate_initializable = std::conjunction<std::is_aggregate<Struct>, is_direct_list_initializable<Struct, T...>, std::negation<std::conjunction<std::bool_constant<sizeof...(T) == 1>, std::is_same<std::decay_t<std::tuple_element_t<0, std::tuple<T...>>>, Struct>>>>; template<typename Struct, typename... T> constexpr bool is_aggregate_initializable_v = is_aggregate_initializable<Struct, T...>::value; 

This does not look very good, but it works as expected.

+1
source

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