嵌套名称说明符中使用的不完整类型 `std::variant<...>`

Incomplete type `std::variant<...>` used in nested name specifier

我将以下代码写入了一个名为main.cpp的文件中。 它涉及具有标准类型 std::variant.

的奇怪的重复模板模式 (CRTP)
#include <string>
#include <variant>
#include <vector>

template<typename T>
struct either {
    std::vector<T> arg;
};

template<typename T>
struct maybe_either: std::variant<T, either<maybe_either<T>>> {

    template<typename U>
    maybe_either(U&& v):
      std::variant<T, either<maybe_either<T>>>(std::forward<U>(v)) {
    }
};

struct var {
  std::string name;
};

int main(int, char**) {
    auto expression = maybe_either<var>(either<maybe_either<var>>{});
    std::visit([&](auto&& v) {
        using T = std::decay_t<decltype (v)>;
        if constexpr (std::is_same_v<T, var>) {
          // ...
        } else if constexpr (std::is_same_v<T, either<maybe_either<var>>>) {
          // ...
        }
    }, expression);
    return 0;
}

使用以下命令行编译时,出现以下错误信息:

$ g++ -c -std=c++17 main.cpp
In file included from main.cpp:2:0:
/usr/include/c++/7/variant: In instantiation of ‘constexpr const size_t std::variant_size_v<maybe_either<var> >’:
/usr/include/c++/7/variant:702:10:   required from ‘struct std::__detail::__variant::__gen_vtable<void, main(int, char**)::<lambda(auto:1&&)>&&, maybe_either<var>&>’
/usr/include/c++/7/variant:1255:23:   required from ‘constexpr decltype(auto) std::visit(_Visitor&&, _Variants&& ...) [with _Visitor = main(int, char**)::<lambda(auto:1&&)>; _Variants = {maybe_either<var>&}]’
main.cpp:32:18:   required from here
/usr/include/c++/7/variant:97:29: error: incomplete type ‘std::variant_size<maybe_either<var> >’ used in nested name specifier
     inline constexpr size_t variant_size_v = variant_size<_Variant>::value;
                             ^~~~~~~~~~~~~~
/usr/include/c++/7/variant: In instantiation of ‘constexpr const auto std::__detail::__variant::__gen_vtable<void, main(int, char**)::<lambda(auto:1&&)>&&, maybe_either<var>&>::_S_vtable’:
/usr/include/c++/7/variant:711:29:   required from ‘struct std::__detail::__variant::__gen_vtable<void, main(int, char**)::<lambda(auto:1&&)>&&, maybe_either<var>&>’
/usr/include/c++/7/variant:1255:23:   required from ‘constexpr decltype(auto) std::visit(_Visitor&&, _Variants&& ...) [with _Visitor = main(int, char**)::<lambda(auto:1&&)>; _Variants = {maybe_either<var>&}]’
main.cpp:32:18:   required from here
/usr/include/c++/7/variant:711:49: error: ‘_S_apply’ was not declared in this scope
       static constexpr auto _S_vtable = _S_apply();
                                         ~~~~~~~~^~

我的class maybe_either 派生自std::variant<...> 可以在其他上下文中正常使用,但是当我在其上调用std::visit(...) 时,它无法编译。怎么了?

这基本上就是LWG3052 which I'm trying to address in P2162

maybe_either<T> 不是 std::variant 的特化 - 它继承自 std::variantstd::visit 目前未指定。完全不清楚允许访问哪些类型的“变体”。

libstdc++ 实现了该库问题中最初建议的方向,即 std::variant 的专业化(您不是)。另一方面,libc++ 允许访问继承自 std::variant 的类型,因此它 accepts your example.

意图是示例 as-is 最终将变为 well-formed。但在那之前,您必须确保您的访问是直接在 std::variant 上进行的。您可以通过添加自己的成员或 non-member visit 来为您执行此操作,这样调用者就不必自己执行此操作。

例如,这个:

template<typename T>
struct maybe_either: std::variant<T, either<maybe_either<T>>> {
    using base = typename maybe_either::variant;

    template<typename U>
    maybe_either(U&& v):
      std::variant<T, either<maybe_either<T>>>(std::forward<U>(v)) {
    }

    template <typename F>
    decltype(auto) visit(F&& f) & {
        return std::visit(std::forward<F>(f), static_cast<base&>(*this));
    }
};

允许这个 to work:

int main(int, char**) {
    auto expression = maybe_either<var>(either<maybe_either<var>>{});
    expression.visit([&](auto&& v) {
        using T = std::decay_t<decltype (v)>;
        if constexpr (std::is_same_v<T, var>) {
          // ...
        } else if constexpr (std::is_same_v<T, either<maybe_either<var>>>) {
          // ...
        }
    });
    return 0;
}