5

I want to write a class template M that accepts incomplete type C as template parameter. But I also want C has some traits when it is eventually defined.

Is this code guaranteed

  • to compile if defined(FLAG),
  • and to fail the compiling if !defined(FLAG)?
template <auto> struct Dummy {};

template <typename C>
void check()
{
    static_assert(std::is_trivial_v<C>);
}

template <typename C>
struct M : Dummy<&check<C>>
{
    //static_assert(std::is_trivial_v<C>);//error: incomplete type
    C * p;
};

struct Test;
M<Test> m;

int main()
{
    return 0;
}

#if defined(FLAG)
struct Test {};
#else
struct Test { std::string non_trivial_member; };
#endif
dfrib
  • 70,367
  • 12
  • 127
  • 192
zwhconst
  • 1,352
  • 9
  • 19
  • I suspect at `M` that your code is already ill-formed ndr. But am not certain. I have vague memory of a clause involving at point of instantiation and end of compilation unit, intended to give the compiler freedom to defer instantiating the `Check` body, but requiring either spot to give the same result. – Yakk - Adam Nevraumont May 25 '21 at 11:22
  • @Yakk-AdamNevraumont: As I remember, rules are different for classes and functions. class is instantiated only once, whereas function instantiation should "respect" ODR (and so be identical for each POI (and EOF is a possible POI)). – Jarod42 May 25 '21 at 12:45
  • so issue would be with `check` but not `M`. – Jarod42 May 25 '21 at 12:46
  • @Jarod42 But the POI of `check` on `struct M : Dummy<&check>` is the `M` line, no? Ie, if `Test` where defined after `M` was, `M` would be ok (anything else is madness). – Yakk - Adam Nevraumont May 25 '21 at 13:32
  • @Yakk-AdamNevraumont: I would say `M` instantiates `check`, and for EOF `check` again. So `M` is instantiated once with incomplete `Test`, and check twice, one with incomplete `Test`, and one with complete `Test`. – Jarod42 May 25 '21 at 13:43

1 Answers1

4

From a n4713,

Point of instantiation [temp.point] (17.7.4.1/8)

A specialization for a function template, a member function template, or of a member function or static data member of a class template may have multiple points of instantiations within a translation unit, and in addition to the points of instantiation described above, for any such specialization that has a point of instantiation within the translation unit, the end of the translation unit is also considered a point of instantiation. A specialization for a class template has at most one point of instantiation within a translation unit. A specialization for any template may have points of instantiation in multiple translation units. If two different points of instantiation give a template specialization different meanings according to the one-definition rule (6.2), the program is ill-formed, no diagnostic required.

First, note that the primary template being paa0ssed arguments a specialization in standard speak. C++ programmers use it differently than the standard does in my experience.

Second, check<Test> has two points of instantiation in your program; once at

M<Test> m;

and once at the end of the translation unit.

The meaning of check<Test> at M<Test> m is different than the meaning of check<Test> at the end of the translation unit. At one spot, Test is incomplete, at the other it is complete. The body of check<Test> definitely has a different meaning.

So your program is ill-formed, no diagnostic required. In your case, the ill-formed program happens to do what you want, but it could (under the standard) compile to anything at all, or fail to compile.

I suspect the reason behind this rule is to give the compiler the freedom to instantiate check either immediately or to defer it until later. You are not allowed to rely on which of the two spots it actually does the instantiation of the body of check.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524