C++变体到变体只有一些重叠类型,为什么我的版本不工作?

rqcrx0a6  于 2023-02-17  发布在  其他
关注(0)|答案(2)|浏览(116)

C17,多编译器。TL;博士,我想问的是:
为什么我的解决方案不能在gcc下工作,而可以在clang和msvc
下工作呢?我是不是忽略了包扩展或逗号分隔表达式求值的微妙之处?
我在这个问题上的变化似乎至少有点独特,事实上,我相信我可以找到一个解决我的问题,但我想知道 * 为什么 * 我的尝试没有工作。
我相信有更好的方法来做到这一点,但我正在寻找理解的语言。
任务:
将std::variant〈A,B,C〉v转换为std::variant〈B,C,D〉*,**前提条件 * 是v已经删除了包含A的格。
把一个变体变成另一个变体有各种各样超级有趣和有教育意义的答案在这里,比如
Assign variant<A,B,C> from variant<C,B>?
最后我将深入研究std::visit及其奇妙的习惯用法。
引导:

struct Cat{};
struct Dog{};
struct Cow{};
struct Illithid{};

int main()
{

    using V = std::variant<Dog,Cat,Cow>;
    using Q = std::variant<Cat,Cow,Illithid>;
    V v = Cat{};
    auto q = transfer_variant<Q>(v);

    return 0;
}

我期望q是类型Q,存储一个Cat。
我的尝试是这样的:

template <typename R, typename ...Ts> R transfer_variant(const std::variant<Ts...>& v)
{

    R r;
    (
       ([](const auto& v, auto& r) {if constexpr (requires{R(std::get<Ts>(v)); }) { if (std::holds_alternative<Ts>(v)) { r = std::get<Ts>(v); } }}(v, r))
       , ...);

    return r;
}

R是返回变量类型,Ts表示存储在源变量中的类型。
基本思想是构造一个默认的R,然后在发现运行时类型与传入类型之一匹配时修改它。我在逗号分隔表达式中使用参数扩展的习惯用法

((value),...)

以评估一系列立即评估的lambda,其中返回值是不相关的并且被丢弃,但是作为副作用,r仅被改变一次。
需要'requires'子句,因为V中的一个类型不在Q中,如果不可能赋值,我必须将其转换为null操作,函数的预期前提条件不可能使v包含这个无效类型,但扩展还是生成了无效表达式。
在clang和Visual Studio 2021下,在写这篇文章的时候,它在trunk附近。它在gcc下不工作,它给出了:

<source>: In instantiation of 'R transfer_variant(const std::variant<_Types ...>&) [with R = std::variant<Cat, Cow, Illithid>; Ts = {Dog, Cat, Cow}]':
<source>:31:33:   required from here
<source>:12:49: error: parameter packs not expanded with '...':
   12 |     (([](const auto& v, auto& r) {if constexpr (requires{R(std::get<Ts>(v)); }) { if (std::holds_alternative<Ts>(v)) { r = std::get<Ts>(v); } }}(v, r)), ...);

clang和msvc++做了我所期望的,最小惊奇法则,但这并不意味着他们的规则是正确的。
(For如果你们想知道我的答案,我在学习std::visit的时候就决定了,我仍然不太确定如何摆脱hokey null指针强制转换来使类型正常工作,但是这可以在所有三个编译器中编译:
{{{EDIT:我完全搞砸了最初从我的源代码复制这个,它完全坏了......我再次重新粘贴这个,希望我得到正确的,在原来的职位了很久之后}}}

template <typename R, typename ...Ts> R transfer_variant(const std::variant<Ts...>& v)
    {
        R(*f[])(const std::variant<Ts...>&) = { [](const std::variant<Ts...>& v) {if constexpr (requires {R(std::get<Ts>(v)); }) { return R(std::get<Ts>(v)); } else { assert(false && "Unhandled type"); return *((R*)nullptr); } } ... };
        return f[v.index()](v);
    }

...它从lambdas中构建一个函数指针表,然后根据运行时索引只调用其中一个,但我仍然想知道我是否足够理解这种语言的原始尝试)

dm7nw8vv

dm7nw8vv1#

为什么我的解决方案不能在gcc下工作,而可以在clang和msvc ++下工作呢?我是不是忽略了包扩展或逗号分隔表达式求值的微妙之处?
GCC hassomeissues用参数包的扩展为模板lambda和requires-子句的组合,所以这是GCC的一个bug。

wz3gfoph

wz3gfoph2#

您可以使用模板化的lambda来解决此错误:

template <typename R, typename... Ts>
R transfer_variant(const std::variant<Ts...>& v) {
    R r;
     ([]<typename T>(const auto& v, auto& r) {
        if constexpr (requires { R(std::get<T>(v)); }) {
            if (std::holds_alternative<T>(v)) {
                r = std::get<T>(v);
            }
        }}.template operator()<Ts>(v, r)
     , ...);

     return r;
}

这在gcc中有效。
顺便说一句:我认为这张支票:

if constexpr (requires { R(std::get<T>(v)); })

可简化为

if constexpr (std::is_constructible_v<R, T>)

相关问题