Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ GCC Why this sfinae code can be compiled with GCC 4.7, but not with 4.8?

I like to use local classes in template classes to perform constructions like "static if". But I've faced with the problem that gcc 4.8 does not want to compile my code. However 4.7 does.

This sample:

#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();
}

Options:

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

Task:

  1. Given classes A and B with different interfaces for printing.
  2. Write a generic class Test that can print both A and B.
  3. Do not pollute either any namespace or class scope.

Description of the code:

  1. This is only a clean example.
  2. I use an approach like this, because I want to generalize the construction "static if". See, that I pass the arguments to IfA and IfB classes via their fields, not directly to the print() function.
  3. I use such constructions a lot.
  4. I've found that these constructions should not be in (pollute) class scope. I mean they should be placed in a method scope.

So the question.

This code can not be compiled with GCC 4.8. Because it checks ALL classes, even if they are never used. But it has not instantiate them in binary (I've commented the lines that cause errors and compiled it with gcc 4.8). Proof:

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

See, there is no Test::print()::IfB::print(). (See later: 'void Test::print()::IfB::print() [with T = A]')

The errors if I compile aforementioned code with 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. Is it a GCC 4.8 bug?
  2. Or is it GCC 4.7 bug? Maybe the code should not be compiled.
  3. Or it is a my bug, and I should not rely on the compiler behavior/should not use such approach to implement "static if".

Additional info:

This simple code compiles on 4.7, but not on 4.8. I shortened it.

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();
}

Errors:

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();
                 ^

Have tested two GCC 4.8 versions: 2012.10 and 2013.02. Hope it is GCC 4.8 bug and it can be fixed.

like image 645
cppist Avatar asked Mar 17 '13 03:03

cppist


2 Answers

LocalClass is not a template. The "not instantiated if not used" rule is only applicable to member functions of class templates.

That is, when Test::print() is instantiated, everything that is inside is brought to life, including the unused member of its local class.

like image 117
n. 1.8e9-where's-my-share m. Avatar answered Nov 19 '22 06:11

n. 1.8e9-where's-my-share m.


There is no SFINAE in your code.

SFINAE applies during template argument deduction and argument substitution (the 'S' in SFINAE stands for substitution) but the only substitution in your program happens when substituting A for T in the template parameter list of Test, which doesn't fail.

You then call print() which instantiates Test<A>::print(), which doesn't involve any substitution, and you get an error because value.notExist(); is not valid.

SFINAE has to be used in substitution contexts, such as template argument deduction caused by a function call or when deducing template parameters with default arguments.

like image 38
Jonathan Wakely Avatar answered Nov 19 '22 05:11

Jonathan Wakely