C ++ GCC为什么这个sfinae代码可以用GCC 4.7编译,但不能用4.8编译?

时间:2013-03-17 03:50:15

标签: c++ gcc c++11 sfinae

我喜欢在模板类中使用本地类来执行“static if”之类的构造。但是我遇到了gcc 4.8不想编译代码的问题。但是4.7确实如此。

此示例:

#include <type_traits>
#include <iostream>
#include <string>

using namespace std;

struct A {
    void printA() {
        cout << "I am A" << endl;
    }
};
struct B {
    void printB() {
        cout << "I am B" << endl;
    }
};

template <typename T>
struct Test {
    void print() {
        struct IfA {
            constexpr IfA(T &value) : value(value) {
            }
            T &value;
            void print() {
                value.printA();
            }
        };
        struct IfB {
            constexpr IfB(T &value) : value(value) {
            }
            T &value;
            void print() {
                value.printB();
            }
        };
        struct Else {
            constexpr Else(...) {}
            void print() {
            }
        };
        typename conditional<is_same<T, A>::value, IfA, Else>::type(value).print();
        typename conditional<is_same<T, B>::value, IfB, Else>::type(value).print();
    }
    T value;
};

int main() {
    Test<A>().print();
    Test<B>().print();
}

选项:

g++ --std=c++11 main.cc -o local-sfinae

任务:

  1. 鉴于A类和B类具有不同的打印接口。
  2. 编写可以打印A和B的通用类Test。
  3. 不要污染任何名称空间或类范围。
  4. 代码说明:

    1. 这只是一个干净的例子。
    2. 我使用这样的方法,因为我想概括构造“静态if”。请注意,我通过字段将参数传递给IfA和IfB类,而不是直接传递给print()函数。
    3. 我经常使用这样的结构。
    4. 我发现这些结构不应该在(污染)类范围内。我的意思是他们应该放在方法范围内。
    5. 所以问题。

      此代码无法使用GCC 4.8进行编译。因为它检查所有类,即使它们从未使用过。但是它没有以二进制形式实例化它们(我已经注释了导致错误的行并用gcc 4.8编译它)。证明:

      $ nm local-sfinae |c++filt |grep "::If.*print"
      0000000000400724 W Test<A>::print()::IfA::print()
      00000000004007fe W Test<B>::print()::IfB::print()
      

      看,没有Test :: print():: IfB :: print()。 (见后面:'void Test :: print():: IfB :: print()[with T = A]')

      如果我使用gcc 4.8编译上述代码时出错:

      g++ --std=c++11 main.cc -o local-sfinae
      main.cc: In instantiation of 'void Test<T>::print()::IfB::print() [with T = A]':
      main.cc:36:9:   required from 'void Test<T>::print() [with T = A]'
      main.cc:49:21:   required from here
      main.cc:34:17: error: 'struct A' has no member named 'printB'
                       value.printB();
                       ^
      main.cc: In instantiation of 'void Test<T>::print()::IfA::print() [with T = B]':
      main.cc:28:9:   required from 'void Test<T>::print() [with T = B]'
      main.cc:50:21:   required from here
      main.cc:26:17: error: 'struct B' has no member named 'printA'
                       value.printA();
                       ^
      
      1. 是GCC 4.8错误吗?
      2. 或者是GCC 4.7错误?也许不应该编译代码。
      3. 或者这是我的错误,我不应该依赖编译器行为/不应该使用这种方法来实现“static if”。
      4. 其他信息:

        这个简单的代码在4.7上编译,但不在4.8上编译。我缩短了它。

        struct A {
            void exist() {
            }
        };
        
        template <typename T>
        struct Test {
            void print() {
                struct LocalClass {
                    constexpr LocalClass(T &value) : value(value) {
                    }
                    T &value;
                    void print() {
                        value.notExist();
                    }
                };
            }
            T value;
        };
        
        int main() {
            Test<A>().print();
        }
        

        错误:

        main.cc: In instantiation of 'void Test<T>::print()::LocalClass::print() [with T = A]':
        main.cc:16:9:   required from 'void Test<T>::print() [with T = A]'
        main.cc:22:21:   required from here
        main.cc:14:17: error: 'struct A' has no member named 'notExist'
                         value.notExist();
                         ^
        

        测试了两个GCC 4.8版本:2012.10和2013.02。希望它是GCC 4.8的bug,它可以修复。

2 个答案:

答案 0 :(得分:3)

LocalClass不是模板。 “未实例化但未使用”规则仅适用于类模板的成员函数。

也就是说,当实例化Test::print()时,内部的所有内容都会生动,包括其本地类的未使用成员。

答案 1 :(得分:3)

您的代码中没有SFINAE。

SFINAE在模板参数推断和参数替换期间应用(SFINAE中的'S'代表替换)但是在您的程序中唯一替换是在A替换模板参数列表中的T时发生的。 Test,这不会失败。

然后调用print()实例化Test<A>::print(),它不涉及任何替换,并且由于value.notExist();无效而收到错误。

SFINAE必须在替换上下文中使用,例如函数调用引起的模板参数推导或使用默认参数推导模板参数时。