0

I am trying to get static_assert to help me avoid null pointers in C++11.

The problem seems to be that C++11 require the compiler to compile templates even if they are not instantiated.

I have the following code:

#include <type_traits>

template<typename T, typename... Us>
std::enable_if_t< std::is_constructible<T, Us...>::value == true, T * >
create_if_constructible(Us... args) { return new T(args...); }

template<typename T, typename... Us>
std::enable_if_t< std::is_constructible<T, Us...>::value == false, T * >
create_if_constructible(Us... args) { 
   static_assert( false, "Class T constructor does not match argument list.");
   return nullptr; 
}

struct ClassA {
   ClassA(int a, string b) {}
};

void foo() {
   ClassA *a = create_if_constructible<ClassA>(1, "Hello");
   // ClassA *b = create_if_constructible<ClassA>(1, "Hello", "world"); // I want compile time error here.
}

I would like this to compile without error. But the static_assert is compiled and gives me a compile time error.

Only if the the second instantiation of the ClassA is in the code should it give me a compile time error.

Generic Name
  • 1,083
  • 1
  • 12
  • 19
  • 2
    I'm confused - even without `create_if_constructible` you get a compiler error if you provide the wrong number of arguments to the `ClassA` constructor... how's what you're doing leading to "helping [you] avoid null pointers"? – Tony Delroy Apr 06 '15 at 09:51
  • 1
    Anyway... if you change your `static_assert()` inside the "false" version to `static_assert(std::is_constructible::value, "Class T constructor does not match argument list.");` it will do what you seem to want.... – Tony Delroy Apr 06 '15 at 10:01
  • @TonyD I believe that will still run afoul of the "no valid specialization can be generated" rule and so is still ill-formed no diagnostic required (there's no set of template arguments that will make the template signature valid and yet not trigger the `static_assert`). A workaround is straight forward though - `template class always_false : std::false_type {};` and `static_assert` on `always_false::value`. But I'm also not seeing the point of this code. – T.C. Apr 06 '15 at 10:40
  • @T.C.: you're referring to 14.6/8? I don't see why it would apply, and the text doesn't make it clear to me that there needs to be a condition where the `static_assert` doesn't trigger. It seems intuitive to me that the template should be selected based on signature, *then* the `static_assert` kick in. Could you share your understanding of the relevance? Thanks. – Tony Delroy Apr 06 '15 at 10:53
  • @TonyD The problem is in the `enable_if` considered together with `is_constructible` - regardless of the value of `is_constructible<...>`, either the return type is invalid (if it's true), or the static assert fires (if it's false). As a result, there's no way you can generate a valid specialization for it. – T.C. Apr 06 '15 at 10:55
  • @T.C.: 14.6/8 is specifically a check on "the syntax of every template" - given the *syntax* of `is_constructible<...>`'s use is correct, it doesn't seem to me to be valid for the compiler to deem there to be "no valid specialisation" until instantiation, when the actual compile-time-constant *value* of `is_constructible` is considered. Under 14.6/8's "Note": If a template is instantiated, errors will be diagnosed according to the other rules in this Standard. Exactly when these errors are diagnosed is a quality of implementation issue." I'd expect this to be one of these instantiation errors – Tony Delroy Apr 06 '15 at 11:13
  • @TonyD `static_assert(false, "");` is perfectly valid syntatically. – T.C. Apr 06 '15 at 11:14
  • @T.C. true - so is it a compiler bug or my misreading... odds aren't looking good for me O_o. – Tony Delroy Apr 06 '15 at 11:15
  • @TonyD Note that the example immediately following that paragraph permits `p = i;` to be diagnosed for `int i` and `char *p`, though `p = i;` is also syntactically valid. Knowing which names are type names allows checking of syntax (and without the ability to check syntax you can't check semantic constraints), but the permission to diagnose is broader. – T.C. Apr 06 '15 at 11:19
  • @T.C. Another angle on this is - regardless of the syntax-checking-only question re 14.6/8, is not `std::is_constructible::value` - being a T-dependent value - *required* to defer evaluation until instantiation under Two-Phase Name Lookup rules? That could be considered to prohibit the comparison of `enable_if` condition with `static_assert` condition that leads to the insight that no valid specialisation is possible. (FWIW, I'll point out that it does happen to work on the GCC compiler I tried, though that's just one sample point). – Tony Delroy Apr 06 '15 at 11:25
  • @TonyD I'm aware of no rule in the standard that requires it to be deferred. `std::is_constructible::value` is a dependent name, to be sure, but that doesn't require deferral of checking. – T.C. Apr 06 '15 at 11:42
  • @TonyD I can think of one argument against this type of checking in the general case - if `std::is_constructible::value` is some special type rather than`bool`, then given sufficiently devious operator overloads you can have a case that would result in a valid specialization. However, a sufficiently smart compiler can ignore this possibility in this case because specializing `is_constructible` is UB. Considering the implementation costs, it's unlikely that we'll see a compiler doing this any time soon, but it's always better to write standard-conforming code. – T.C. Apr 06 '15 at 11:43

2 Answers2

3

The standard permits, but does not require, compilers to diagnose templates for which no valid instantiation can be generated. This can range from simple syntax errors to your example of a constant false expression in a static_assert. §14.6 [temp.res]/p8:

If no valid specialization can be generated for a template, and that template is not instantiated, the template is ill-formed, no diagnostic required.

I'm rather baffled by all this SFINAE machinery, though. A simple

template<typename T, typename... Us>
T* create_if_constructible(Us... args) { return new T(args...); }

already refuses to compile if T is not constructible from the parameter given, so I'm not sure how this complex circumlocution will help you "avoid null pointers".

Regardless, a simple way to make choosing the second function template a compile-time error is to explicitly delete it.

template<typename T, typename... Us>
std::enable_if_t< std::is_constructible<T, Us...>::value == false, T * >
create_if_constructible(Us... args) = delete;

Alternatively, if you are partial to static_asserts, perhaps because of the custom error message, you must ensure that there is theoretically a way to generate a valid instantiation of your template. That means that 1) what you are static_asserting on must depend on a template argument, and 2) there must be theoretically a way for the condition to be true. A simple way is to use an auxiliary template:

template<class> class always_false : std::false_type {};

template<typename T, typename... Us>
std::enable_if_t< std::is_constructible<T, Us...>::value == false, T * >
create_if_constructible(Us... args) { 
   static_assert( always_false<T>::value, "Class T constructor does not match argument list.");
   return nullptr; 
}

The key point here is that the compiler cannot assume that always_false<T>::value is always false because it is always possible that there's a specialization later that sets it to true, and so it is not allowed to reject this at template definition time.

T.C.
  • 133,968
  • 17
  • 288
  • 421
  • I agree with your comments on my post and then I've decided to remove it and +1 yours. However, for the benefit of others, I would like to leave the best part of it which is this [reference](http://blog.llvm.org/2009/12/dreaded-two-phase-name-lookup.html) on two-phase name lookup. – Cassio Neri Apr 06 '15 at 16:54
0

In all C++ standards ever, templates are compiled in two phases. The second phase is instantiation, but compilation can also fail in phase 1. In particular, syntax errors are detected in phase 1.

In your case, the simpler solution is to leave out the body of the second instantiation.

Another solution is to use T in the static_assert, so the compiler must delay evaluation to phase 2. Trivially: static_assert(sizeof(T)==0,

MSalters
  • 173,980
  • 10
  • 155
  • 350
  • `static_assert(sizeof(T)==0, /*... */);` is still ill-formed NDR. – T.C. Apr 06 '15 at 10:57
  • Commpailer complains when leaving out the body of the function. And sizeof(T)==0 still evaluates the funciton even though it is not used. – Generic Name Apr 06 '15 at 11:16
  • @T.C.: How is that ill-formed? – MSalters Apr 06 '15 at 13:40
  • @user2099460: `sizeof` isn't a function, and does not evaluate it's argument. `sizeof(*(int*)nullptr)` is perfectly legal. – MSalters Apr 06 '15 at 13:41
  • @MSalters Because there is no `T` for which that assertion will not fire. – T.C. Apr 06 '15 at 19:32
  • @T.C.: Sure, but that's irrelevant. The expression is syntactically valid and furthermore the whole point of `static_assert(false, "diagnostic")` is that a diagnostic is required. – MSalters Apr 06 '15 at 20:37
  • A template for which no valid specialization can be generated is ill-formed by itself, effectively meaning that the static_assert can still fire at template definition time with a sufficiently smart compiler. – T.C. Apr 06 '15 at 21:13
  • @T.C.: I think you may be right. [This answer](http://stackoverflow.com/questions/24420340/custom-compile-error-message-when-undefined-subtype-is-accessed) explains why the technique is basically sound, but the subtle difference is that it involves specializations whereas this involves overloading. – MSalters Apr 07 '15 at 00:04
  • @MSalters I wrote that answer :) Also, that one uses a `sizeof...(Pack)`, which can validly be zero (for an empty pack), not plain `sizeof`, which can't. – T.C. Apr 07 '15 at 00:06