首页 > 解决方案 > MSVC 可以找到匹配的构造函数,但 gcc 和 clang 找不到

问题描述

所以最近我对制作一个带标签的元组很感兴趣,它是一个元组,但你可以使用带有标签而不是索引的 get 函数。代码对我来说有点长。

// tagged_tuple.hpp
// simple class to hold two types
// works with incomplete types, important for our use case
template <class First, class Second> struct type_pair {
    using first = First;
    using second = Second;
};

namespace detail {
    // gets info about a classes name tag (key -> value type mapping) from the class
    template <class T>
    struct name_tag_traits {
        // aliases for our specific use case
        using tag_type = typename T::first;
        using value_type = typename T::second;
    };

    // helper alias, turns a list of TypePairs supplied to tagged_tuple to a list of
    // key/tag/name types
    template <class T>
    using name_tag_t = typename name_tag_traits<T>::tag_type;

    // same as above but returns a list of value types
    template <class T>
    using name_tag_value_t = typename name_tag_traits<T>::value_type;

    template <class T> struct Base {};
    template <class... Ts>
    struct TypeSet : Base<Ts>... {
        template <class T>
        constexpr auto operator + (Base<T>) {
            if constexpr (std::is_base_of_v<Base<T>, TypeSet>) {
                return TypeSet{};
            }
            else {
                return TypeSet<Ts..., T>{};
            }
        }
        constexpr auto size() const -> std::size_t { return sizeof...(Ts); }
    };

    // checks if tags are unique
    template <class... Types>
    constexpr auto are_tags_unique() -> bool {
        constexpr auto typeset = (TypeSet<>{} + ... + Base<name_tag_t<Types>>{});
        return typeset.size() == sizeof...(Types);
    }

    template <class Needle>
    constexpr auto index_of_impl(size_t index, size_t end) -> std::size_t {
        return end;
    };

    template <class Needle, class T, class... Haystack>
    constexpr auto index_of_impl(size_t index, size_t end) -> std::size_t {
        return std::is_same<Needle, T>::value
            ? index
            : index_of_impl<Needle, Haystack...>(index + 1, end);
    };

    // find the index of T in a type list, returns sizeof...(Haystack) + 1 on failure (think std::end())
    template <class Needle, class... Haystack>
    static constexpr auto index_of() -> std::size_t {
        return index_of_impl<Needle, Haystack...>(0, sizeof...(Haystack) + 1);
    };

}; // namespace detail

// and here's our little wrapper class that enables tagged tuples
template <class... TypePairs>
class tagged_tuple : public std::tuple<detail::name_tag_value_t<TypePairs>...> {
public:
    // throws an error if tags are not unique
    static_assert(detail::are_tags_unique<TypePairs...>(), "Duplicated tags!");
    // not really needed for now but if we switch to private inheritance it'll come in handy
    using tag_type = std::tuple<detail::name_tag_t<TypePairs>...>;
    using value_type = std::tuple<detail::name_tag_value_t<TypePairs>...>;
    using value_type::value_type;
    using value_type::swap;
    using value_type::operator =;
};

// our special get functions
template <class Name, class... TypePairs>
auto get(tagged_tuple<TypePairs...>& tuple) -> typename std::tuple_element<
    detail::index_of<Name, detail::name_tag_t<TypePairs>...>(),
    typename tagged_tuple<TypePairs...>::value_type>::type& {
    return std::get<detail::index_of<Name, detail::name_tag_t<TypePairs>...>()>(
        tuple);
};

template <class Name, class... TypePairs>
auto get(const tagged_tuple<TypePairs...>& tuple) -> const typename std::tuple_element<
    detail::index_of<Name, detail::name_tag_t<TypePairs>...>(),
    typename tagged_tuple<TypePairs...>::value_type>::type& {
    return std::get<detail::index_of<Name, detail::name_tag_t<TypePairs>...>()>(
        tuple);
};

template <class Name, class... TypePairs>
auto get(tagged_tuple<TypePairs...>&& tuple) -> typename std::tuple_element<
    detail::index_of<Name, detail::name_tag_t<TypePairs>...>(),
    typename tagged_tuple<TypePairs...>::value_type>::type&& {
    return std::get<detail::index_of<Name, detail::name_tag_t<TypePairs>...>()>(
        std::move(tuple));
};

语法是:

// main.cpp
using foo = tagged_tuple<type_pair<class bar, int>, type_pair<class baz, std::string>>;
foo a{ 1, "foo" };
std::cout << get<bar>(a) << " " << get<baz>(a);

输出:

1 foo

一切都很顺利,直到我测试了这段代码:

std::tuple<int, std::string> a{ 1, "foo" };
foo b{ a };

在我的理论中应该可以工作,因为 tagged_tuple 是从 std::tuple 派生的,并且它有一个针对这种情况的构造函数。然后我在 Visual Studio 2019 中进行了测试,它完全有效。但是当我尝试使用 gcc/clang 时,我得到了这个错误:

 error: no matching constructor for initialization of 'foo' (aka 'tagged_tuple<type_pair<class bar, int>, type_pair<class baz, std::string>>')
    foo b{ a };

这很奇怪,因为我已经包含了所有基类的构造函数using value_type::value_type(据我所知)。然后我添加了以下构造函数:

template <class... Types>
constexpr tagged_tuple(std::tuple<Types...> &tuple) : value_type(tuple) {}

而这一次它奏效了。我不明白为什么没有这段代码它就不能工作。

gcc/clang 中是否有错误或我遗漏了什么?

标签: c++gccvisual-c++clang

解决方案


推荐阅读