代码之家  ›  专栏  ›  技术社区  ›  Guillaume Racicot

clang 6和clang 7之间的模板转换运算符差异

  •  8
  • Guillaume Racicot  · 技术社区  · 6 年前

    我有一些代码,使用模板转换运算符找到通过ADL找到的函数的返回类型。

    #include <type_traits>
    
    template<typename S>
    struct probe {
        template<typename T, typename U = S, std::enable_if_t<
            std::is_same<T&, U>::value &&
            !std::is_const<T>::value, int> = 0>
        operator T& ();
    
        template<typename T, typename U = S&&, std::enable_if_t<
            std::is_same<T&&, U>::value &&
            !std::is_const<T>::value, int> = 0>
        operator T&& ();
    
        template<typename T, typename U = S, std::enable_if_t<
            std::is_same<T const&, U>::value, int> = 0>
        operator T const& () const;
    
        template<typename T, typename U = S&&, std::enable_if_t<
            std::is_same<T const&&, U>::value, int> = 0>
        operator T const&& () const;
    };
    
    namespace foo {
        struct bar {};
    
        auto find_me(bar const&) -> int { return 0; } 
    }
    
    int main() {
        // That would be inside a template in my code.
        find_me(probe<foo::bar>{});
    }
    

    在clang6和GCC中,编译上述代码。但是,在Clang7中,它不再编译了!

    https://godbolt.org/z/Lfs3UH

    probe<foo::bar>::operator foo::bar&&<foo::bar, foo::bar&&, 0>() 但是clang7失败了,因为它试图调用 probe<foo::bar>::operator const foo::bar&&<const foo::bar, foo::bar&&, 0>()


    我想查查很多案子。不仅仅是 foo::bar

    namespace foo {
        struct bar {};
    
        auto find_me(bar const&) -> int { return 0; } 
        auto find_me(bar&&) -> int { return 0; } 
        auto find_me(bar const&&) -> int { return 0; } 
        auto find_me(bar&) -> int { return 0; } 
    }
    
    int main() {
        find_me(probe<foo::bar>{});
        find_me(probe<foo::bar&>{});
        find_me(probe<foo::bar&&>{});
        find_me(probe<foo::bar const&>{});
        find_me(probe<foo::bar const&&>{});
    }
    

    解析正确的函数调用非常重要。

    https://godbolt.org/z/yrDFMg

    2 回复  |  直到 6 年前
        1
  •  1
  •   Oliv    6 年前

    clang 6/7和gcc之间的行为差异可以通过以下简化的示例代码来说明:

    #include <type_traits>
    
    struct S{
        template<class T,class=std::enable_if_t<!std::is_const_v<T>>>
        operator T& ();
    };
    
    void test() {
        S a;
        const int& i = a; //Accepted by Gcc and clang 6 accept, rejected by clang 7
    }
    

    Gcc和clang6接受代码,clang7拒绝代码。

    在Gcc的情况下 T=int T=const int T=常数int T=常数int 如果禁用,则clang 7拒绝代码。

    根据 [over.match.ref] :

    考虑了S及其基类的转换函数。 那些不隐藏在S中的非显式转换函数,产生对cv2 T2的左值引用(当初始化对函数的左值引用或右值引用时)或对cv2 T2的cv2 T2或右值引用(当初始化对函数的右值引用或左值引用时),其中cv1 T是与cv2 T2兼容的引用,是候选函数。 对于直接初始化,那些未隐藏在S中的显式转换函数和分别产生对cv2 T2或cv2 T2的类型左值引用或对cv2 T2的类型右值引用的显式转换函数(其中T2与T的类型相同或可以通过限定转换转换为T类型)也是候选函数。

    在我们的案例中,这意味着 S int& const int& 可能是候选人。

    [temp.deduct.conv] :

    转换的结果 (称之为;请参阅[dcl.init]、[over.match.conv]和[over.match.ref]以确定该类型,如[temp.DETRIBUTE.type]中所述。

    1. 海合会认为 转换的结果 并不意味着 转换序列的结果

    2. 转换的结果 这意味着什么 . 它只对 T=cont int .

    从我在标准中读到的内容来看,我不能说什么是对标准的“正确”解释。不过,我认为,一般来说,叮当行为与模板参数推导更为一致:

    template<class T,class=std::enable_if_t<std::is_const_v<T>>>
    void f(T& x);
    
    void test(){
      int i;
      f(i);
      // If considering that the argument type is int caused
      // template argument deduction failure, then template argument
      // deduction would be performed for a const int argument.
      // But template argument deduction succeeds. So T is deduced to int. 
      // Only after this deduction template argument substitution happens.
      // => both gcc and clang reject this code.
      }
    
        2
  •  1
  •   Jans    6 年前

    我相信这与 Bug 32861 original report . clang 7 .

    template<typename T, typename U = S&&, std::enable_if_t<
        std::is_same<T&&, U>::value &&
        !std::is_const<T>::value, int> = 0>
    operator T&& ();
    

    在里面 clang 6 扣除到 T T=bar std::is_same<T&&, U>::value 是真的,但是 扣除额将是 T=bar const 现在这种特质不再存在了,

    还要注意的是 扣除额为 T=钢筋混凝土 也会导致 !std::is_const<T>::value