1

I am looking for a solution to create constructor accepting all std::initializer_lists accepted by given T. For example:

#include <initializer_list>
#include <utility>

template <typename T> struct AllInits
{
  AllInits(std::initializer_list<?>){}
};

struct ManyListTypes
{
  ManyListTypes(std::initializer_list<bool>){}
  ManyListTypes(std::initializer_list<int>){}
  ManyListTypes(std::initializer_list<std::pair<int, int>>){}
};

int main()
{
  AllInits<ManyListTypes> first{true,false,true};
  AllInits<ManyListTypes> second{1,2,3};
  AllInits<ManyListTypes> third{{1,1},{2,2},{3,3}};
  return 0;
}

Is it even possible?

wehin19066
  • 33
  • 4

1 Answers1

1

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.
HolyBlackCat
  • 78,603
  • 9
  • 131
  • 207
  • 1
    *"it relies on some arcane tricks, and probably shouldn't be used in practice."* See [is-stateful-metaprogramming-ill-formed-yet](https://stackoverflow.com/questions/44267673/is-stateful-metaprogramming-ill-formed-yet) – Jarod42 Nov 03 '21 at 10:42