Search code examples
c++c++11static-assert

Conditional compilation of templates


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.


Solution

  • 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.