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};
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.