The best practical solution would be:
template <typename T>
struct AllInits
{
template <typename U>
requires std::constructible_from<T, std::initializer_list<U> &>
AllInits(std::initializer_list<U>){}
};
This passes the first two testcases, but fails the third one. I.e. you can't have nested braced lists.
A possible workaround is to specify the element types:
AllInits<ManyListTypes> third{std::pair{1,1}, std::pair{2,2}, std::pair{3,3}};
There is a solution that doesn't have this limitation, but it relies on some arcane tricks, and probably shouldn't be used in practice.
Run on gcc.godbolt.org
#include <concepts>
#include <cstddef>
#include <initializer_list>
#include <iostream>
#include <type_traits>
#include <utility>
// Some helpers.
template <typename T>
struct Tag
{
using type = T;
};
template <typename ...P>
struct TypeList
{
inline static constexpr std::size_t size = sizeof...(P);
};
template <typename T>
void PrintType()
{
#ifndef _MSC_VER
std::cout << __PRETTY_FUNCTION__ << '\n';
#else
std::cout << __FUNCSIG__ << '\n';
#endif
};
// A generic stateful-template list.
constexpr void adl_ListElem() {} // A dummy ADL target.
template <typename Key, std::size_t Index>
struct ElemViewer
{
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wnon-template-friend"
#endif
friend constexpr auto adl_ListElem(ElemViewer);
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic pop
#endif
};
template <typename Key, std::size_t Index, typename Value>
struct ElemWriter
{
friend constexpr auto adl_ListElem(ElemViewer<Key, Index>) {return Tag<Value>{};}
};
template <typename Key, typename Unique, std::size_t Index = 0, typename = void>
struct NumElems : std::integral_constant<std::size_t, Index> {};
template <typename Key, typename Unique, std::size_t Index>
struct NumElems<Key, Unique, Index, decltype(adl_ListElem(ElemViewer<Key, Index>{}), void())> : std::integral_constant<std::size_t, NumElems<Key, Unique, Index+1, void>::value> {};
template <typename Key, typename Value>
struct ElemInserter : ElemWriter<Key, NumElems<Key, Value>::value, Value>, std::true_type {};
// Constructor detection.
template <typename T>
struct AnyInitList
{
template <
typename U,
typename = std::enable_if_t<std::is_same_v<U, std::initializer_list<typename U::value_type>>>,
typename = std::enable_if_t<ElemInserter<T, typename U::value_type>::value>
>
operator U() const
{
return {};
}
};
template <typename T, typename = void>
struct DetectCtors {};
template <typename T>
struct DetectCtors<T, decltype(T(AnyInitList<T>{}), void())> {};
template <typename Type, typename I>
struct ElemListLow {};
template <typename T, std::size_t ...I>
struct ElemListLow<T, std::index_sequence<I...>>
{
static constexpr TypeList<typename decltype(adl_ListElem(ElemViewer<T, I>{}))::type...> helper() {}
using type = decltype(helper());
};
template <typename T>
struct ElemList : ElemListLow<T, std::make_index_sequence<NumElems<T, decltype(DetectCtors<T>{}, void())>::value>> {};
// Generating a type with the suitable constructors.
template <typename T>
struct SingleCtor
{
SingleCtor() {}
SingleCtor(std::initializer_list<T>) {}
};
template <typename T, typename>
struct MultiCtor;
template <typename T, typename ...P>
struct MultiCtor<T, TypeList<P...>> : SingleCtor<P>...
{
using SingleCtor<P>::SingleCtor...;
using list = TypeList<P...>;
template <typename U>
requires std::constructible_from<T, std::initializer_list<U> &>
MultiCtor(std::initializer_list<U>){}
};
// The final type.
template <typename T>
struct Foo : MultiCtor<T, typename ElemList<T>::type>
{
using MultiCtor<T, typename ElemList<T>::type>::MultiCtor;
};
// Demo.
struct ManyListTypes
{
ManyListTypes(std::initializer_list<bool>){}
ManyListTypes(std::initializer_list<int>){}
ManyListTypes(std::initializer_list<std::pair<int, int>>){}
};
int main()
{
PrintType<Foo<ManyListTypes>::list>(); // TypeList<bool, int, std::pair<int, int>>
Foo<ManyListTypes> first{true,false,true};
Foo<ManyListTypes> second{1,2,3};
Foo<ManyListTypes> third{{1,1},{2,2},{3,3}};
}
There is a lot going on here.
class AnyInitList
has a templated conversion operator to std::initializer_list<??>
.
- An attempt is made to construct your type (
ManyListTypes
) from AnyInitList
, but we don't care if it succeeds, any errors are hidden by SFINAE.
- This process instantiates
AnyInitList
for all types that the constructors of ManyListTypes
can accept. We then use stateful metaprogramming to collect those types into a list.
- The rest is easy. We create
class SingleCtor<T>
that's constructible from std::initializer_list<T>
, then inherit the final type from several specializations of SingleCtor
(inheriting constructors), one per type in the list we got.