14

I'm looking for a way to automatically make default template parameter be unique each time a template is instantiated. Since unnamed function objects created by lambda expressions have different types I thought of adopting them somehow. With recent changes to standard daft removing "A lambda-expression shall not appear in ... a template-argument" restriction (see Wording for lambdas in unevaluated contexts) it seemed like a good idea. So I wrote the following kinda working snippet that compiles on recent gcc and clang:

#include <type_traits>

template<void ( * ) (void) = [](){}> class
unique final {};

static_assert(false == ::std::is_same_v<unique<>, unique<>>);

int main()
{
    return 0;
}

Is this a viable approach or one of those "ill-formed, no diagnostic is required" cases?

Some additional context: I want to use this to implement Ada-style strong type definitions that should work in a single translation unit without manually inventing unique tags that would be otherwise unused:

struct _tag_WowInt {};
using Int = type<int, _tag_WowInt>;
struct _tag_SoUnique {};
using DifferentInt = type<int, _tag_SoUnique>;

Upd1: I would like to mention that approaches involving __COUNTER__ or similar macros won't work in general case because they will be expanded by preprocessor only once and won't yield unique types when used inside of template for example.

user7860670
  • 35,849
  • 4
  • 58
  • 84

1 Answers1

2

I believe that you are right, it seems to me that is "ill-formed, no diagnostic required". I think this is covered by [temp.res/8.4] and [temp.res/8.5]:

(8.4) ― a hypothetical instantiation of a template immediately following its definition would be ill-formed due to a construct that does not depend on a template parameter, or

(8.5) ― the interpretation of such a construct in the hypothetical instantiation is different from the interpretation of the corresponding construct in any actual instantiation of the template. [Note: This can happen in situations including the following:

(8.5.1) ― a type used in a non-dependent name is incomplete at the point at which a template is defined but is complete at the point at which an instantiation is performed, or

(8.5.2) ― lookup for a name in the template definition found a using-declaration, but the lookup in the corresponding scope in the instantiation does not find any declarations because the using-declaration was a pack expansion and the corresponding pack is empty, or

(8.5.3) ― an instantiation uses a default argument or default template argument that had not been defined at the point at which the template was defined, or

(8.5.4) ― constant expression evaluation within the template instantiation uses

(8.5.4.1) ― the value of a const object of integral or unscoped enumeration type or

(8.5.4.2) ― the value of a constexpr object or

(8.5.4.3) ― the value of a reference or

(8.5.4.4) ― the definition of a constexpr function, and that entity was not defined when the template was defined, or

(8.5.5) ― a class template specialization or variable template specialization that is specified by a non-dependent simple-template-id is used by the template, and either it is instantiated from a partial specialization that was not defined when the template was defined or it names an explicit specialization that was not declared when the template was defined. — end note]

Even though your use case is not explicitly listed in the examples of the note, in my understanding the requirement implies that unique<> must refer to the same thing throughout the whole program, otherwise it is ill-formed, no diagnostic required.

This was CWG1850. The Committee appear to dislike this kind of stateful meta-programming. The constexpr counter no longer works in newer versions of the compilers.

metalfox
  • 6,301
  • 1
  • 21
  • 43
  • 1
    8.4 does not seem to be applicable at all. Which construct not depending on template parameters would be ill-formed here?. And i'm not sure about 8.5 either. 1) "the construct" referenced there seems to refer to some part of the template, not to the template instantiations; 2) instantiating template with default parameters is allowed to yield different results in different scopes because default template parameters may be redefined (when used as template template parameter for example). – user7860670 Jan 30 '19 at 10:39
  • @VTT 8.4 does not apply, but it defines what a "hypothetical intantiation" is. – metalfox Jan 30 '19 at 14:21
  • @VTT there's a newer version of the paper you linked ([P0315R4](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0315r4.pdf)) where the authors claim that defining `foo<[]{}>()` in two TU should be an ODR violation. – metalfox Jan 30 '19 at 14:25
  • 1
    @VTT interestingly, in GCC trunk `template class unique {}` yields always the same type (https://godbolt.org/z/SO1PZl). – metalfox Jan 30 '19 at 14:29
  • 3
    @Rakete1111 I see. That enables crazy things to be made: https://wandbox.org/permlink/ACtX4SdSJqc2MOHz – metalfox Apr 03 '19 at 09:28
  • @metalfox getting internal compiler error in Visual Studio 2022 on C++ 20. When I remove the default template argument, and then call the counter with: ` std::cout << counter() << '\n';`, it then starts to work and generates 2 functions: counts 0 to 9 two times. This compiles for me on MSVC: https://wandbox.org/permlink/giR7ZazQJyzla8VJ . Any suggestions to make this work on MSVC without macros? – KulaGGin Aug 02 '23 at 15:06