Implementing std::variant converting constructor - or: how to find first overload of all conversions from any T to Ti from parameter pack

后端 未结 1 1989
失恋的感觉
失恋的感觉 2021-02-06 05:14

In the latest working draft (page 572) of the C++ standard the converting constructor of std::variant is annotated with:

template  co         


        
相关标签:
1条回答
  • 2021-02-06 06:12

    The technique I'll describe is to actually build an overload set, and perform overload resolution by attempting to call it and see what happens with std::result_of.

    Building the Overload Set

    We define a function object that recursively defines an T operator()(T) const for each T.

    template <typename T>
    struct identity { using type = T; };
    
    template <typename... Ts> struct overload;
    
    template <> struct overload<> { void operator()() const; };
    
    template <typename T, typename... Ts>
    struct overload<T, Ts...> : overload<Ts...> {
      using overload<Ts...>::operator();
      identity<T> operator()(T) const;
    };
    
    // void is a valid variant alternative, but "T operator()(T)" is ill-formed
    // when T is void
    template <typename... Ts>
    struct overload<void, Ts...> : overload<Ts...> {
      using overload<Ts...>::operator();
      identity<void> operator()() const;
    };
    

    Performing Overload Resolution

    We can now use std::result_of_t to simulate overload resolution, and find the winner.

    // Find the best match out of `Ts...` with `T` as the argument.
    template <typename T, typename... Ts>
    using best_match = typename std::result_of_t<overload<Ts...>(T)>::type;
    

    Within variant<Ts...>, we would use it like this:

    template <typename T, typename U = best_match<T&&, Ts...>>
    constexpr variant(T&&);
    

    Some Tests

    Alright! Are we done? The following tests pass!

    // (1) `variant<string, void> v("abc");` // OK
    static_assert(
        std::is_same_v<std::string,
                       best_match<const char*, std::string, void>>);
    
    // (2) `variant<string, string> w("abc");` // ill-formed
    static_assert(
        std::is_same_v<std::string,
                       best_match<const char*, std::string, std::string>>);
    
    // (3) `variant<string, bool> x("abc");` // OK, but chooses bool
    static_assert(
        std::is_same_v<bool,
                       best_match<const char*, std::string, bool>>);
    

    Well, we don't want (2) to pass, actually. Let's explore a few more cases:

    No viable matches

    If there are no viable matches, the constructor simply SFINAEs out. We get this behavior for free in best_match, because std::result_of is SFINAE-friendly as of C++14 :D

    Unique Match

    We want the best match to be a unique best match. This is (2) that we would like to fail. For example, we can test this by checking that the result of best_match appears exactly once in Ts....

    template <typename T, typename... Ts>
    constexpr size_t count() {
      size_t result = 0;
      constexpr bool matches[] = {std::is_same_v<T, Ts>...};
      for (bool match : matches) {
        if (match) {
          ++result;
        }
      }
      return result;
    }
    

    We can then augment this condition onto best_match in a SFINAE-friendly way:

    template <typename T, typename... Ts>
    using best_match_impl = std::enable_if_t<(count<T, Ts...>() == 1), T>;
    
    template <typename T, typename... Ts>
    using best_match = best_match_impl<std::result_of_t<overload<Ts...>(T)>, Ts...>;
    

    Conclusion

    (2) now fails, and we can simply use best_match like this:

    template <typename T, typename U = best_match<T&&, Ts...>>
    constexpr variant(T&&);
    

    More Tests

    template <typename> print;  // undefined
    
    template <typename... Ts>
    class variant {
      template <typename T, typename U = best_match<T&&, Ts...>>
      constexpr variant(T&&) {
        print<U>{}; // trigger implicit instantiation of undefined template error.
      }
    };
    
    // error: implicit instantiation of undefined template
    // 'print<std::__1::basic_string<char> >'
    variant<std::string> v("abc");
    
    // error: no matching constructor for initialization of
    // 'variant<std::string, std::string>'
    variant<std::string, std::string> w("abc");
    
    // error: implicit instantiation of undefined template 'print<bool>'
    variant<std::string, bool> x("abc");
    
    0 讨论(0)
提交回复
热议问题