When does gcc compile unused template code?
Asked Answered
L

2

2

I have the following (admittedly contrived) code that compiles just fine in gcc 6, but doesn't compile in gcc 7. Notice the use of an undeclared constructor in the definition of bar. This should print an error if the function is ever referenced elsewhere in the code (uncommenting foo.bar() causes gcc 6 to print an error). However, gcc 7 prints an error even if the function is not used.

Some changes cause the code to also compile with gcc 7 (e.g. if B is replaced with T in the definition of A), while some changes cause it to fail with gcc 6 (e.g. if this-> is not used). What's going on here? When does gcc decide to compile unused template code? Do different versions of gcc use different rules to decide?

struct B {};

template <typename T>
struct A {

    B* bar()
    {
        // undeclared constructor
        return new B(this->b);
    }

    B* b;
};

int main (int argc, char* argv[])
{
    A<int> foo;

    //foo.bar();
}
Lipfert answered 25/2, 2018 at 3:0 Comment(6)
"Because no implicit copy constructor is defined with a pointer argument, " - if it were, it would not be a copy constructor.Boondoggle
fair enough. not sure what else to call such a made up function though.Lipfert
It's just a constructor.Boondoggle
Exceptions are thrown. Compilation errors are printed.Colophon
thank you, i'll correct my terminologyLipfert
The compiler rejects your code even without the A<int> instantiation, because it can prove that whatever T, the function bar can never be valid. But it doesn't have to, it was added because it is considered helpful.Earthquake
C
1

A::bar() is a non-template member function in a template class. If it were itself a template, SFINAE would allow the code to compile when bar() is not called. But the way you have it now, once A is instantiated with some template arguments, all of it is expected to be valid.

One solution would be:

template <typename T>
struct A {

    template <typename X>
    X* bar()
    {
        // static_assert(is_same<X, B>) if you want
        return new X(this->b);
    }

    B* b;
};

Then you'd call a.bar<B>() instead of a.bar(), and if you don't call it, it won't be instantiated and won't cause an error.

Cogon answered 25/2, 2018 at 3:4 Comment(2)
but why does it compile with some versions of gcc and not others?Lipfert
@willpett: C++ compilers in general and GCC specifically tend to accept a whole lot of code which is technically imperfect. Over time, some of these "not quite C++ but close enough" programs start to get rejected by newer compilers as the compilers gain more powerful features related to optimization, or more formalized internal mechanisms that are more strict. So while GCC 6 was not wrong to accept your code, GCC 7 is also not wrong to reject it. GCC 4 accepted quite a few things that GCC 6 rejects, too.Cogon
B
0

Perhaps I'm missing something, but you appear to be trying to construct a B object from a B pointer. And there is no default constructor that does that. So surely you want:

struct B {
    B( B * b ) {
    }
};
Boondoggle answered 25/2, 2018 at 3:25 Comment(5)
Yes, but my point is that I was surprised to find older versions of gcc compile it just fine even without this declaration, while gcc 7 throws an error.Lipfert
Well, older versions of GCC have more bugs in them than do later ones, or at least we so hope - I think the latest one is right in regard to your codeBoondoggle
There are no obscure updates to the standard. Whether your code should compile or not I'm really not qualified to say, but what the hell, I'll say "no".Boondoggle
Is that corroborated somewhere in the standard?Lipfert
My Standard trawling days are long past - but I can recommend trawling it if you really want to learn C++.Boondoggle

© 2022 - 2024 — McMap. All rights reserved.