23

There are cases where one uses an always_false helper to e.g. cause unconditional static_assert failure if instantiation of some template is attempted:

template <class... T> struct always_false : std::false_type {};

template<class T>
struct UsingThisShouldBeAnError {
  static_assert(always_false<T>::value, "You should not use this!");
};

This helper is necessary because a template definition must (at least theoretically) have at least one set of template parameters for which a valid specialization can be produced in order for the program to be well-formed:

[temp.res]/8: The program is ill-formed, no diagnostic required, if:

  • no valid specialization can be generated for a template [...] and the template is not instantiated, or

[...]

(Writing static_assert(false, "You should not use this!"); above would thus be ill-formed and a compiler could always fire the static assert, even without the template being instantiated, which is not the intention.)

Here is a quick sampling of questions involving this pattern (including further explanation):

It might be useful to have always_false as a tool in the standard library so we don't have to constantly write it again. However, the answer to the following question makes me wonder whether this is even possible:

Dependent non-type parameter packs: what does the standard say?

There the argument is made (also with respect to [temp.res]/8) that std::enable_if_t<T> is always either void or not a type and that it is illegal for anyone to specialize it further. Therefore, a template that relies on the theoretical "specializability" of std::enable_if to avoid the [temp.res]/8 clause actually causes the program to be ill-formed, no diagnostic required.

Coming back to my question: If the standard provided always_false, it would have to forbid library users from specializing it as usual (for obvious reasons). But by the above reasoning, that would defeat the whole point of always_false (namely that it could theoretically be specialized to something other than std::false_type) - with respect to [temp.res]/8 it would be the same as using std::false_type directly.

Am I wrong in this reasoning? Or is it actually impossible for the standard library to provide always_false in a meaningful/useful way (without core language changes)?

Community
  • 1
  • 1
Max Langhof
  • 23,383
  • 5
  • 39
  • 72
  • the standard library can do things that are otherwise illegal in user code, so I would say yes, it is possible for the standard library to implement such a construct, even if it were illegal otherwise. Don't quote me though. – bolov Sep 04 '19 at 11:57
  • 1
    @bolov: issue is user's **usage**. class definition is trivial and doesn't have issue. but once in `std`, that imposes extra restriction to user (as no specialization unless allowed). – Jarod42 Sep 04 '19 at 12:01
  • @Jarod42 god, C++ can be have such dark corners... – bolov Sep 04 '19 at 12:02

4 Answers4

16

In C++20, with lambda, you might do something like:

template <class... T> struct always_false : std::false_type {};

// To have true, but for a type that user code can't reuse as lambda types are unique.
template <> struct always_false<decltype([](){})> : std::true_type{};

After reflection, I think it is not possible: enclosing template might have other restrictions that the hypothetical type(s) for the specialization should fulfill:

With static_assert(is_enum_v<T> && always_false_v<T>), that type should be an enum.

And even more constrained, with static_assert(is_same_v<T, int> && always_false_v<T>), it is for int.

Edit: C++23 now allows static_assert(false); in non instantiated part :)

Jarod42
  • 203,559
  • 14
  • 181
  • 302
15

To paraphrase Jarod's idea, It could be something like

template <class... T> struct always_false : std::false_type {};

template <> struct always_false</* implementation defined */> : std::true_type{};

Where /* implementation defined */ can be filled by std::_ReservedIdentifer. User code can't access it, since the identifier is reserved to the library, but there exists a specialization that is true. That should avoid questions about the ODR and lambdas in specializations.

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
3

All such attempts result in a program that is ill-formed, no diagnostic required.

The clauses that block you from using static_assert(false) make your program ill-formed, no diagnostic required based on the actual possibility of making an actual instantiation, not based on if the compiler can work it out.

These tricks simply make it harder for the compiler to detect the fact your program is ill-formed. The diagnostic they issue is not required, and your ability to bypass the diagnostic being issued just means you made the compiler produce an ill-formed program for which the standard places no restrictions on its behavior.

(Writing static_assert(false, "You should not use this!"); above would thus be ill-formed and a compiler could always fire the static assert, even without the template being instantiated, which is not the intention.)

The exact same conclusion holds for your

template <class... T> struct always_false : std::false_type {};

template<class T>
struct UsingThisShouldBeAnError {
  static_assert(always_false<T>::value, "You should not use this!");
};

I claim there is no valid instantiation of UsingThisShouldBeAnError in the above program.

http://eel.is/c++draft/temp.res#6.1

The program is ill-formed, no diagnostic required, if: (6.1) no valid specialization can be generated for a template [...]"

No valid specialization can be generated for this template.

To avoid this trap, your program must have

template <> struct always_false<SomeListOfTypes> : std::true_type {};

and if an always_false is specified in the standard for which this cannot occur, using always_false from the standard doesn't help you at all. because the standard requires that the specialization "can be generated".

If the only way to instantiate your template is within an ill-formed program, then that is a huge stretch on the word "can". So using reserved or magic types you aren't allowed to use within the true_type specialization is not really plausible.

Stepping back, the purpose of the "ill-formed, ndr" there is because the standard writers want to permit diagnostics of broken code, but don't want to mandate it. Detecting such broken code in general is a halt-hard problem, but simple cases can be detected. And optimizing around the assumption that the code isn't broken can be useful.

All attempts to inject static_assert(false, "message") are working around the intention that C++ code is intended to be valid.

We have a construct for a function for whom successful lookup is an error already in the language.

template<class T>
void should_never_be_found(tag<T>) = delete;

the hole, of course, is the lack of a diagnostic message here.

template<class T>
void should_never_be_found(tag<T>) = delete("Provide an custom ADL overload!");

also, the inability to =delete template specializations.

What you appear to want is:

template<class T>
struct UsingThisShouldBeAnError = delete("You should not use this");

which is direct, intentional, and consistent with how other =delete cases work.

The static_assert bypass requires magic and "tricking" the compiler to bypass parts of the standard explicitly written to prevent you from doing what you want to do.

This =delete("string") and =delete syntax on template classes is missing from the language.

康桓瑋
  • 33,481
  • 5
  • 40
  • 90
Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • *"your program must have"*. How much code can be added to make a specialization valid? From your answer, it seems none. Do you mean that SFINAE based on a type_traits (such as `has_foo`) is ill-formed NDR if there doesn't exist a class which fulfills the requirement (`template void foo(T& t) -> decltype(t.foo()) { return t.foo(); }` is potentially ill-formed). – Jarod42 Oct 14 '21 at 19:02
  • I agree with your overall assessment and your suggestion for the solution (`=delete`) of the overall problem. However the point of the question is to explore the negation of *"if an `always_false` is specified in the standard for which this cannot occur"*: How *can* we specify `always_false` to allow it to occur, in a useful manner (accepting the contradiction to the name)? I believe that `SomeListOfTypes` = `/*implementation defined*/` hits the mark with "there exists at least one legally usable type that results in `true_type`, it's just intentionally hard to name it". – Max Langhof Oct 14 '21 at 19:39
  • @MaxLanghof We cannot. Either the user is allowed to use a type in `SomeListOfTypes` in C++ code, in which case it doesn't satisfy your requirement, or the user code cannot, in which case there is no valid specialization for the template. Remember, everything within `std` header files is literally magic in C++. What code constructs in them are valid is not relevant. I mean, we could go with "the name of the type that works here is implementation defined", but users must be able to name it and using it must be valid. – Yakk - Adam Nevraumont Oct 14 '21 at 20:15
  • Any and *all* hacks around that are either "I am trying to fool the compiler" or "I am trying to confuse or convince the user". Fooling the compiler fundamentally doesn't work, because the standard is being omniscient here; if the only valid type is one that exists if the Collatz conjecture is true, then your program is ill formed depending on the truth value of the Collatz conjecture. – Yakk - Adam Nevraumont Oct 14 '21 at 20:18
  • 1
    @Yakk-AdamNevraumont The point is that there are options between "using the name is forbidden by punishment of ill-formedness, NDR" and "there's a commonly known way to get `std::always_false` to become `std::true_type`". Again, I agree with this being suboptimal (and in the realm of "confuse or convince the user"), but such is life. `std::array` also must have its actual data member public and user-accessible, and the world doesn't come crashing down. – Max Langhof Oct 15 '21 at 07:59
  • *"If the only way to instantiate your template is within an ill-formed program,"*. It would not be ill-formed in compiler/std context though. Might that count? – Jarod42 Oct 15 '21 at 10:53
-6

just use option -fdelayed-template-parsing

吴带爷
  • 27
  • 3
  • `-fdelayed-template-parsing` is an option of some compilers to enable non-standard behavior. But my question is about a theoretical addition to the standard library while keeping standard-compliant behavior. It's not even related to compilers. – Max Langhof Dec 04 '20 at 17:03