Force template instantiation via typedef : success at g++ , fail at Visual C++

≡放荡痞女 提交于 2019-12-08 15:32:01

问题


I want to force template instantiation.
The below code works (print 1) at g++ ( http://coliru.stacked-crooked.com/a/33986d0e0d320ad4 ).
However, it prints wrong result (0) at Visual C++ ( https://rextester.com/WGQG68063 ).

#include <iostream>
#include <string>
template <int& T>struct NonTypeParameter { };

//internal implementation
int lala=0;
template <typename T> struct Holder{
    static int init;
};
template <typename T> int Holder<T>::init = lala++;

//tool for user 
template <typename T> struct InitCRTP{ 
    using dummy=NonTypeParameter<Holder<T>::init>;
};

class WantInit : public InitCRTP<WantInit>{};//user register easily
int main(){
    std::cout << lala << std::endl;
}

Is it a Visual C++ compiler bug, or a kind of undefined behavior?
If it is Visual C++ bug, how to workaround it (while still keep it beautiful)?

Edit: Change class -> struct as Max Langhof (and many people) recommended. Thank.

Bounty Reason

With opposite solutions from StoryTeller and Maxim Egorushkin and their in-depth discussion (thank!), this sounds like a fuzzy area of C++ rule.

If it is Visual C++ bug, I wish the issue to be certain enough to report.

Moreover, I still wish for a nice workaround, because this technique is very shiny for custom type-id generation. Explicit instantiation is not so convenient.

Note: I awarded bounty to Kaenbyou Rin, because, for me, it is easy to understand.
It doesn't means that the rest of answers are less correct or less useful.
I am still not sure which is a correct one. Readers should proceed with caution.
For safety, I will assume that I just can't use the feature (for now). Thanks everyone.


回答1:


There is a compiler bug involved, for sure. We can verify it by changing InitCRTP a bit:

template <typename T, typename = NonTypeParameter<Holder<T>::init>>
struct InitCRTP {
};

Now referring to any InitCRTP<T> specialization must use Holder<T>::init to determine the second template argument. This in turn should force an instantiation of Holder<T>::init, and yet VS doesn't instantiate that.

In general, using the CRTP class as a base should have instantiated all the declarations inside the class, including that of dummy. So that too should have worked.

We can verify it further. Declarations of member functions are instantiated along with the class, when used as a base:

template <typename T> struct InitCRTP{
    using dummy=NonTypeParameter<Holder<T>::init>;
    void dummy2(dummy);
};

Still, VC++ is stubborn. Given all of this, and the behavior exhibited by both Clang and GCC, this is a VC++ bug.




回答2:


class WantInit : public InitCRTP<WantInit> does neither instantiate InitCRTP<WantInit>::dummy, nor Holder<WantInit>::init because they aren't referred to by something actually used in the program. The implicit instantiation chain in your code doesn't require instantiating Holder<T>::init, see implicit instantiation:

This applies to the members of the class template: unless the member is used in the program, it is not instantiated, and does not require a definition.

A fix is to use explicit template instantiation:

template struct Holder<void>;

That causes Holder<void> to be instantiated along with all its non-template members.

Alternatively, you can instantiate just Holder<T>::init member, e.g.:

static_cast<void>(Holder<void>::init);

IMO, gcc and clang are overly eager to instantiate things that aren't referred to. Such a behaviour doesn't break or reject valid code, so that is hardly a bug, but depending on such a specific behaviour for side effects is brittle and non-portable.




回答3:


Let's try to definitely ODR-use the init member.

#include <iostream>
#include <string>

int lala=0;
template <typename T> struct Holder{
    static int init;
};
template <typename T> int Holder<T>::init = lala++;

template <typename T> struct InitCRTP{
    InitCRTP() { (void)Holder<T>::init; }
};

class WantInit : public InitCRTP<WantInit>{};
int main(){
    std::cout << lala << std::endl;
    // WantInit w;  <---------------------------- look here
}

Now the outcome of the program changes if the commented-out line is uncommented. IMHO template instantiation status or ODR-use status of anything cannot possibly depend on whether or not some non-template function (the WantInit constructor in this case) is called. I would say there is a rather strong smell of a bug.




回答4:


I believe @MaximEgorushkin is right about the fact that dummy is not really instantiated.

dummy is declared (because it is a type alias declaration), and in order to declare this alias, NonTypeParameter<Holder<T>::init> is declared. In order to declare NonTypeParameter<Holder<T>::init>, its template parameter Holder<T>::init has to be declared, thus Holder<T> is also declared.

The standard demands that when a template class is instantiated, its deleted member functions are defined. [temp.spec]

The implicit instantiation of a class template specialization causes: [...]

—— The implicit instantiation of the definitions of deleted member functions, unscoped member enumerations, and member anonymous unions.

And a reference to void should result in a compile error.

We can use this to test whether a specific template is specialized or not.

#include <iostream>
#include <string>
template <int& T, typename U> struct NonTypeParameter { 
    U& f() = delete;
};

//internal implementation
int lala = 0;
template <typename T> struct Holder {
    T& f() = delete;
    static int init;
};
template <typename T> int Holder<T>::init = lala++;

//tool for user 
template <typename T> struct InitCRTP {
    using dummy = NonTypeParameter<Holder<T>::init, void>;
};

class WantInit : public InitCRTP<WantInit> {};//user register easily
int main() {
    std::cout << lala << std::endl;
}

This code would compile because NonTypeParameter<Holder<T>::init, void> is only declared, not instantiated.

But if we change class WantInit : public InitCRTP<WantInit> into

class WantInit : public InitCRTP<void>

Fails to compile in MSVC, g++ and clang.

This is because the declaration of NonTypeParameter<Holder<void>::init, void> needs the implicit instantiation of Holder<void>.

The problem OP has encountered is solely due to MSVC's ignorance of Holder<T>::init being ODR-used:

#include <iostream>

template <int& T> struct NonTypeParameter { };

int lala = 0;

template <typename T> struct Holder {
    static int init;
};

template <typename T> int Holder<T>::init = lala++;

int main() {
    NonTypeParameter<Holder<int>::init> odr;
    std::cout << lala << std::endl;
}

MSVC would output 0. This means it doesn't realize that Holder<int>::init has been ODR-used.

Compiler Explorer Link



来源:https://stackoverflow.com/questions/57953599/force-template-instantiation-via-typedef-success-at-g-fail-at-visual-c

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!