V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Tony042
V2EX  ›  C++

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

  •  
  •   Tony042 · 2020-08-26 09:01:22 +08:00 · 2368 次点击
    这是一个创建于 465 天前的主题,其中的信息可能已经有所发展或是发生改变。

    我又来问问题啦,这回还是喜闻乐见的 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;
    }
    
    15 条回复    2020-08-27 10:31:52 +08:00
    secondwtq
        1
    secondwtq  
       2020-08-26 09:50:57 +08:00
    瞎改了下把 GCC 改过了 ...
    报错的地方全加个 :: ,变成 ::VariantChoice<Types, Types...> 就行了
    Tony042
        2
    Tony042  
    OP
       2020-08-26 10:04:58 +08:00
    @secondwtq 试了一下确实编译过去啦,估计是 gcc 对模板类继承范围查询出了偏差,谢谢指导~
    secondwtq
        3
    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
        4
    Tony042  
    OP
       2020-08-26 10:33:07 +08:00
    @secondwtq 这个没法删,删了之后就得手写 Variant 的 constructor 了,估计是 gcc 的 bug,看来 gcc bug 还是相对较多的
    0x11901
        5
    0x11901  
       2020-08-26 11:16:25 +08:00
    这就是为什么能用 clang 我都尽量用 clang,gcc 真的不大行
    lewis89
        6
    lewis89  
       2020-08-26 11:52:59 +08:00
    @0x11901 #5 主要还是 C++ 编译器太难实现了...
    zsl199512101234
        7
    zsl199512101234  
       2020-08-26 13:34:08 +08:00
    @lewis89 这句话真赞👍
    wutiantong
        8
    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
        9
    wutiantong  
       2020-08-26 14:13:50 +08:00
    另外,我个人感觉 clang11 的 Bug 有点多,远不如 clang10
    Tony042
        10
    Tony042  
    OP
       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
        11
    wutiantong  
       2020-08-26 23:56:35 +08:00   ❤️ 1
    @Tony042 你好像没有正确理解我的意思,因为我提到的改动并没有改变任何代码含义。与其文字解释不如直接看一下代码:
    https://godbolt.org/z/srW9xo
    Tony042
        12
    Tony042  
    OP
       2020-08-27 01:51:26 +08:00
    @wutiantong 是我理解的有偏差,这样改是没有改变代码含义的,谢谢啦
    Tony042
        13
    Tony042  
    OP
       2020-08-27 01:55:37 +08:00
    @wutiantong emmm 这样改,clang 就编译不过去了。。。心塞
    Tony042
        14
    Tony042  
    OP
       2020-08-27 01:56:32 +08:00
    @wutiantong 算了,我写宏针对不同编译器,用不同代码好了
    wutiantong
        15
    wutiantong  
       2020-08-27 10:31:52 +08:00
    @Tony042 我找补一下,把 using base-class constructor 那行单独的换回原来的写法:
    using VariantChoice<Types, Types...>::VariantChoice...;

    这样 clang 和 gcc 就都可以了。
    关于   ·   帮助文档   ·   API   ·   FAQ   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   1146 人在线   最高记录 5497   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 30ms · UTC 19:07 · PVG 03:07 · LAX 11:07 · JFK 14:07
    ♥ Do have faith in what you're doing.