Home > Enterprise >  How to detect if template argument is an std::initializer_list
How to detect if template argument is an std::initializer_list

Time:04-20

In the following example, the last line fails because the conversion operator of JSON has to choose between two possibilities:

  1. std::vector<int>::operator=(std::vector<int>&&)
  2. std::vector<int>::operator=(std::initializer_list<int>)

How can I constrain JSON::operator T() so it ignores the initializer_list overload?

struct JSON
{
    using StorageType = std::variant<
        int,
        float,
        std::string,
        std::vector<int>,
        std::vector<float>
    >;
    
    StorageType storage;

    template<class T>
    operator T() const {
        return std::get<T>(storage);
    }
};

int main(int argc, char* argv[])
{
    const JSON json;
    std::vector<int> numbers;
    numbers = json; // more than one operator '=' matches these operands

    return 0;
}

CodePudding user response:

You can use enable_if to disable the conversion of initializer_list

template<class>
constexpr inline bool is_initializer_list = false;
template<class T>
constexpr inline bool is_initializer_list<std::initializer_list<T>> = true;

struct JSON
{
    // ...
    template<class T, std::enable_if_t<!is_initializer_list<T>>* = nullptr>
    operator T() const {
        return std::get<T>(storage);
    }
};

Demo

CodePudding user response:

While the question for how to constrain get to exclude std::initializer_list is legitimate, I think what actually should be checked for is whether T is one of the types represented in the std::variant of StorageType.

You may check if a type is contained in the list of types of a std::variant as follows:

template <typename T, typename VariantT>
static inline constexpr bool variant_contains_type_v = false;

template <typename T, typename ... Ts>
static inline constexpr bool variant_contains_type_v<T, std::variant<Ts...>> = std::disjunction_v<std::is_same<T, Ts>...>;

Your implementation may then be adapted to

template<class T, typename = std::enable_if_t<variant_contains_type_v<T, StorageType>>>
    operator T() const {
    return std::get<T>(storage);
}

https://godbolt.org/z/Pf3x7bEYd

CodePudding user response:

If you have access to C 20, you might use requires instead of SFINAE with std::enable_if:

// Some filtering traits
// Can even be turned into concept
template <typename>
constexpr inline bool is_initializer_list = false;
template <typename T>
constexpr inline bool is_initializer_list<std::initializer_list<T>> = true;

struct JSON
{
    // ...
    template <typename T>
    operator T() const requires(!is_initializer_list<T>)
    {
        return std::get<T>(storage);
    }
};
  •  Tags:  
  • c
  • Related