user2543623
user2543623

Reputation: 1562

C++ implement my own static_assert

As a learning project, I'm writing my own template metaprogramming static_assert. I found a metaprogramming trick online: try to create an array of size 0, which will fail to compile. So I'm using two nearly identical approaches: on Visual Studio, one works and the other doesn't, but I don't understand what's the difference. On g++ 5.4.0, neither works (not even with the "-std=c++14" flag). Why not?

//This correctly aborts the compile on Visual Studio 2015. 
//But on g++ it doesn't work (not even with the "-std=c++14" flag) .
template <bool b>
inline void my_static_assert_function()
{
    char member[b]; //if b is false, this is 0-length and fails to compile. 
}

//On Visual Studio 2015, this does give a warning, but does not
//abort the compile. Why not? It seems virtually identical to the
//previous one. And on g++, it doesn't even warn. 
template <bool b>
struct my_static_assert_struct
{
    char member[b]; //if b is false, this *warns* but compiles.
};

int main()
{
    my_static_assert_function<1 == 2>(); //This aborts the compile, great.
    my_static_assert_struct<1 == 2> c; //This does NOT abort the compile???
}

Question #1 -- why does "g++ -std=c++14 main.cpp" allow this to compile without even a warning? Shouldn't the my_static_assert_function work there? I'm using 5.4.0 for ubuntu.

Question #2 -- on Visual Studio 2015, my_static_assert_function fails to compile, but my_static_assert_struct compiles with a mere warning. But what's the difference? How can one work if the other doesn't?

Upvotes: 2

Views: 2351

Answers (2)

WENDYN
WENDYN

Reputation: 740

Well, this doesn't use templates but I remember seeing this somewhere:

#define __static_assert(con, id) static int assert_ ## id [2 * !!(con) - 1];
#define _static_assert(con, id) __static_assert(con, id) //expanding any macro that could be in `id` argument
#define static_assert(con) _static_assert(con, __COUNTER__)

Explanation:

  • !!(con) makes any non-zero number true (1) and zero becomes false (0)
  • by subtracting one you get -1 when condition is not met and array can't have negative size -> compile time error
  • We don't want to have multiple arrays with same names, for that we use __COUNTER__ macro to compose the name, but for that we need to expand it first

Upvotes: 0

vsoftco
vsoftco

Reputation: 56547

As mentioned by @Kerrek SB in the comments, gcc uses some non-standard ISO C++ extension to allow for zero-sized arrays, although it gives you a warning. A far more elegant alternative is to SFINAE out false via std::enable_if, like so

#include <iostream>
#include <type_traits>

template<bool b, typename std::enable_if<b>::type* = nullptr>
void my_static_assert() 
{
    std::cout << "Assertion OK\n";
}

int main()
{
    my_static_assert < (1 < 2) > (); // ok
    //my_static_assert < (1 > 2) > (); // fails to compile
}

Live on Coliru

Another alternative (I think first proposed by Andrei Alexandrescu) is to leave a generic template non-defined, and define only the true specialization. Then, if you try to instantiate the false specialization, you'll get a compile-time error since you cannot instantiate an incomplete type. Example below:

template<bool> // generic
struct my_static_assert;

template<>
struct my_static_assert<true>{};

int main()
{
    my_static_assert < (1 < 2) >{}; // ok
    my_static_assert < (1 > 2) >{}; // fails to compile
}

Live on Coliru

Upvotes: 2

Related Questions