【问题标题】:C++ GCC Why this sfinae code can be compiled with GCC 4.7, but not with 4.8?C++ GCC 为什么这个 sfinae 代码可以用 GCC 4.7 编译,但不能用 4.8?
【发布时间】:2013-03-17 03:50:14
【问题描述】:

我喜欢在模板类中使用本地类来执行诸如“静态 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. 不要污染任何命名空间或类范围。

代码说明:

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

所以问题。

此代码不能用 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. 或者这是我的错误,我不应该依赖编译器行为/不应该使用这种方法来实现“静态 if”。

附加信息:

这个简单的代码可以在 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,可以修复。

【问题讨论】:

  • 关于标准告诉“无需诊断”仍然无效的格式错误的代码。所以编译器可能会拒绝编译它。

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


【解决方案1】:

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

也就是说,当 Test::print() 被实例化时,里面的所有东西都会被激活,包括它的本地类中未使用的成员。

【讨论】:

    【解决方案2】:

    您的代码中没有 SFINAE。

    SFINAE 适用于模板参数推导和参数替换(SFINAE 中的“S”代表替换),但程序中唯一的替换发生在 A 替换 T 模板参数列表中的 Test 时,这不会失败。

    然后您调用 print() 实例化 Test&lt;A&gt;::print(),这不涉及任何替换,您会收到错误,因为 value.notExist(); 无效。

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

    【讨论】:

    • 我的意思是 包含很多 sfinae 代码。然而,似乎 GCC 4.7 和 4.8 都没有真正实例化 LocalClass。但 GCC 4.8 也返回错误。感谢您的评论。我做了一个更好的微型类型切换机制:ideone.com/v1KmTA
    • 作为@n.m。在另一个答案中说,LocalClass 不是模板,因此无法实例化。它的定义是无效的,当周围的函数模板被实例化时被诊断出来
    猜你喜欢
    • 2013-03-19
    • 2013-08-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-11-09
    • 2013-12-01
    相关资源
    最近更新 更多