clang, msvc 可以编译通过, gcc 不行

2020-08-26 09:01:22 +08:00
 Tony042

我又来问问题啦,这回还是喜闻乐见的 C++问题,同样一份代码 MSCVC 19.24 ,clang 10 均可编译通过,g++-10 不可编译通过,这是什么原因呢,注意,须加入参数要求 compiler 支持 C++17 (-std=c++17), 可在线编译版本代码链接 https://godbolt.org/z/eWhsne,源码如下, 其中开头部分的 type_traits 应该是没问题的,主要问题集中在 gcc 对 fold expression 的处理上,这份代码是对 std::variant 部分简单不完全实现

#include <type_traits>
#include <utility>
#include <new>
#include <cassert>
#include <exception>

template <bool COND, typename TrueType, typename FalseType>
class IfThenElseT
{
public:
    using Type = TrueType;
};

template <typename TrueType, typename FalseType>
class IfThenElseT<false, TrueType, FalseType>
{
public:
    using Type = FalseType;
};

template <bool COND, typename TrueType, typename FalseType>
using IfThenElse = typename IfThenElseT<COND, TrueType, FalseType>::Type;

template <typename... Elements>
class Typelist
{
};

template <typename List>
class FrontT;

template <typename Head, typename... Tail>
class FrontT<Typelist<Head, Tail...>>
{
public:
    using Type = Head;
};

template <typename List>
using Front = typename FrontT<List>::Type;

template <typename List>
class PopFrontT;

template <typename Head, typename... Tail>
class PopFrontT<Typelist<Head, Tail...>>
{
public:
    using Type = Typelist<Tail...>;
};

template <typename List>
using PopFront = typename PopFrontT<List>::Type;

template <typename List>
class IsEmpty
{
public:
    static constexpr bool value = false;
};

template <>
class IsEmpty<Typelist<>>
{
public:
    static constexpr bool value = true;
};

template <typename List, typename T, unsigned N = 0, bool Empty = IsEmpty<List>::value>
struct FindIndexOf
{
};

template <typename List, typename T, unsigned N>
struct FindIndexOf<List, T, N, false> : public IfThenElse<std::is_same_v<Front<List>, T>,
                                                          std::integral_constant<unsigned, N>,
                                                          FindIndexOf<PopFront<List>, T, N + 1>>
{
};

template <typename List, typename T, unsigned N>
struct FindIndexOf<List, T, N, true>
{
};

template <typename List>
class LargestTypeT;

template <typename List>
class LargestTypeT
{
private:
    using First = Front<List>;
    using Rest = typename LargestTypeT<PopFront<List>>::Type;

public:
    using Type = IfThenElse<(sizeof(First) >= sizeof(Rest)), First, Rest>;
};

template <>
class LargestTypeT<Typelist<>>
{
public:
    using Type = char;
};

template <typename List>
using LargestType = typename LargestTypeT<List>::Type;

template <typename... Types>
class VariantStorage
{
private:
    using LargestT = LargestType<Typelist<Types...>>;
    alignas(Types...) unsigned char buffer[sizeof(LargestT)];
    unsigned char discriminator = 0;

public:
    unsigned char getDiscriminator() { return discriminator; }
    void setDiscriminator(unsigned char d) { discriminator = d; }
    void *getRawBuffer() { return buffer; }
    void const *getRawBuffer() const { return buffer; }

    template <typename T>
    T *getBufferAs() { return std::launder(reinterpret_cast<T *>(buffer)); }

    template <typename T>
    T const *getBufferAs() const { return std::launder(reinterpret_cast<T const *>(buffer)); }
};

template <typename... Types>
class Variant;

template <typename T, typename... Types>
class VariantChoice
{
private:
    using Derived = Variant<Types...>;
    Derived &getDerived() { return *static_cast<Derived *>(this); }
    Derived const &getDerived() const { return *static_cast<Derived const *>(this); }

protected:
    constexpr static unsigned Discriminator = FindIndexOf<Typelist<Types...>, T>::value + 1;

public:
    VariantChoice() = default;
    VariantChoice(T const &value);
    VariantChoice(T &&value);
    bool destroy();
    Derived &operator=(T const &value);
    Derived &operator=(T &&value);
};

template <typename T, typename... Types>
VariantChoice<T, Types...>::VariantChoice(T const &value)
{
    new (getDerived().getRawBuffer()) T(value);
    getDerived().setDiscriminator(Discriminator);
}

template <typename T, typename... Types>
VariantChoice<T, Types...>::VariantChoice(T &&value)
{
    new (getDerived().getRawBuffer()) T(std::move(value));
    getDerived().setDiscriminator(Discriminator);
}

template <typename T, typename... Types>
bool VariantChoice<T, Types...>::destroy()
{
    if (getDerived().getDiscriminator() == Discriminator)
    {
        getDerived().template getBufferAs<T>()->~T();
        return true;
    }
    return false;
}

template <typename T, typename... Types>
auto VariantChoice<T, Types...>::operator=(T const &value) -> Derived &
{
    if (getDerived().getDiscriminator() == Discriminator)
    {
        *getDerived().template getBufferAs<T>() = value;
    }
    else
    {
        getDerived().destroy();
        new (getDerived().getRawBuffer()) T(value);
        getDerived().setDiscriminator(Discriminator);
    }
    return getDerived();
}

template <typename T, typename... Types>
auto VariantChoice<T, Types...>::operator=(T &&value) -> Derived &
{
    if (getDerived().getDiscriminator() == Discriminator)
    {
        *getDerived().template getBufferAs<T>() = std::move(value);
    }
    else
    {
        getDerived().destroy();
        new (getDerived().getRawBuffer()) T(std::move(value));
        getDerived().setDiscriminator(Discriminator);
    }
    return getDerived();
}

class ComputedResultType;

class EmptyVariant : public std::exception
{
};

template <typename... Types>
class Variant : private VariantStorage<Types...>, private VariantChoice<Types, Types...>...
{
    template <typename T, typename... OtherTypes>
    friend class VariantChoice;

public:
    using VariantChoice<Types, Types...>::VariantChoice...;  //g++报错
    using VariantChoice<Types, Types...>::operator=...;  //g++报错

    template <typename T>
    bool is() const;

    template <typename T>
    T &get() &;

    template <typename T>
    T &&get() &&;

    template <typename T>
    T const &get() const &;

    // template <typename R=ComputedResultType, typename Visitor>
    // VisitResult<R, Visitor

    bool empty() const;
    void destroy();
    ~Variant() { destroy(); }

private:
};

template <typename... Types>
template <typename T>
bool Variant<Types...>::is() const
{
    return this->getDiscriminator() == VariantChoice<T, Types...>::Discriminator; //g++报错
}

template <typename... Types>
template <typename T>
T &Variant<Types...>::get() &
{
    if (empty())
    {
        throw EmptyVariant();
    }
    assert(is<T>());
    return *this->template getBufferAs<T>();
}

template <typename... Types>
template <typename T>
T &&Variant<Types...>::get() &&
{
    if (empty())
    {
        throw EmptyVariant();
    }
    assert(is<T>());
    return *this->template getBufferAs<T>();
}

template <typename... Types>
template <typename T>
T const &Variant<Types...>::get() const &
{
    if (empty())
    {
        throw EmptyVariant();
    }
    assert(is<T>());
    return *this->template getBufferAs<T>();
}

template <typename... Types>
void Variant<Types...>::destroy()
{
    // bool results[] = {VariantChoice<Types, Types...>::destroy()...};
    (VariantChoice<Types, Types...>::destroy(), ...);  //g++报错
    this->setDiscriminator(0);
}

int main()
{
    Variant<int> v{17};
    return 0;
}
3020 次点击
所在节点    C++
15 条回复
secondwtq
2020-08-26 09:50:57 +08:00
瞎改了下把 GCC 改过了 ...
报错的地方全加个 :: ,变成 ::VariantChoice<Types, Types...> 就行了
Tony042
2020-08-26 10:04:58 +08:00
@secondwtq 试了一下确实编译过去啦,估计是 gcc 对模板类继承范围查询出了偏差,谢谢指导~
secondwtq
2020-08-26 10:24:47 +08:00
reduce 了半天 case,好像删了 using VariantChoice<Types, Types...>::VariantChoice... 这句就没事了

然后找到了俩 GCC 的 bug report:
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=94310
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=79094
Tony042
2020-08-26 10:33:07 +08:00
@secondwtq 这个没法删,删了之后就得手写 Variant 的 constructor 了,估计是 gcc 的 bug,看来 gcc bug 还是相对较多的
0x11901
2020-08-26 11:16:25 +08:00
这就是为什么能用 clang 我都尽量用 clang,gcc 真的不大行
lewis89
2020-08-26 11:52:59 +08:00
@0x11901 #5 主要还是 C++ 编译器太难实现了...
zsl199512101234
2020-08-26 13:34:08 +08:00
@lewis89 这句话真赞👍
wutiantong
2020-08-26 14:12:02 +08:00
虽然说不清错在哪,但我们在 Variant 里补一行这个:
template<typename T> using Choice = VariantChoice<T, Types...>;

然后把后面的 VariantChoice<XXX, Types...> 都换成 Choice<XXX>,gcc 就没问题了。
wutiantong
2020-08-26 14:13:50 +08:00
另外,我个人感觉 clang11 的 Bug 有点多,远不如 clang10
Tony042
2020-08-26 21:00:47 +08:00
@wutiantong 额,这个不能这么改,这样改就扩大继承的 VariantChoice 的 Constructor 范围了,VariantChoice<T, Types>中的 T 必须是 parameter pack Types 中的一个类型,Variant 本质上是一个半自动的被赋值为 Types 中任意类型的变量,这就是为什么要用 using VariantChoice<Types, Types...>::operator=...; ,第二个 parameter pack 在尖括号里展开,第一个 parameter pack 在外面展开,继承 number=sizeof...(Types)个 constructor,本质上 Variant 继承 VariantChoice 的 constructor 再通过 VariantStorage 手动管理内存
wutiantong
2020-08-26 23:56:35 +08:00
@Tony042 你好像没有正确理解我的意思,因为我提到的改动并没有改变任何代码含义。与其文字解释不如直接看一下代码:
https://godbolt.org/z/srW9xo
Tony042
2020-08-27 01:51:26 +08:00
@wutiantong 是我理解的有偏差,这样改是没有改变代码含义的,谢谢啦
Tony042
2020-08-27 01:55:37 +08:00
@wutiantong emmm 这样改,clang 就编译不过去了。。。心塞
Tony042
2020-08-27 01:56:32 +08:00
@wutiantong 算了,我写宏针对不同编译器,用不同代码好了
wutiantong
2020-08-27 10:31:52 +08:00
@Tony042 我找补一下,把 using base-class constructor 那行单独的换回原来的写法:
using VariantChoice<Types, Types...>::VariantChoice...;

这样 clang 和 gcc 就都可以了。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/701445

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX