Display integer at compile time in static_assert()

假如想象 提交于 2020-01-10 18:21:27

问题


Here is a simplified version of what I'm trying to do

enum First
{
    a,
    b,
    c,
    nbElementFirstEnum,
};
enum Second
{
    a,
    b,
    c,
    nbElementSecondEnum,
};

static_assert(
    First::nbElementFirstEnum == Second::nbElementSecondEnum,
    "Not the same number of element in the enums.");
/*static_assert(  
    First::nbElementFirstEnum == Second::nbElementSecondEnum, 
    "Not the same number of element in the enums." + First::nbElementFirstEnum + " " + Second::nbElementSecondEnum);*/

But I would like to be able to print the value of First::nbElementFirstEnum and Second::nbElementSecondEnum in the assert message (like in the commented version which obviously doesn't work). I have tryed using macro concatenation with "#". I also tryed using variadic templates, retrieveing with %10 each number and adding the '0' character to the value retrieved, but all I get is a constexpr char[].

So my question is how can I get my enums values to be printed in a string literal.

Possible duplicates :

C++11 static_assert: Parameterized error messages

Integrate type name in static_assert output?

The most interesting topic was this one: Printing sizeof(T) at compile time But I don't want to have a warning, or decomment code to know the values.


回答1:


This basically works, although it's possible to break with a little effort (by making V1 and V2 sum to a multiple of 256). So, I think your solution is uglier but still more robust.

template <int V1, int V2> struct AssertEquality
{
    static const char not_equal_warning = V1 + V2 + 256;
};

template <int V> struct AssertEquality<V, V>
{
    static const bool not_equal_warning = 0;
};

#define ASSERT_EQUALITY(V1, V2) static_assert( \
    AssertEquality<static_cast<int>(V1), \
                   static_cast<int>(V2)>::not_equal_warning == 0, \
    #V1 " != " #V2 );

// ...

ASSERT_EQUALITY(First::nbElementFirstEnum, Second::nbElementSecondEnum);

with output looking like:

g++ -std=c++0x -c chksz.cpp
chksz.cpp: In instantiation of ‘const char AssertEquality<3, 2>::not_equal_warning’:
chksz.cpp:40:124:   instantiated from here
chksz.cpp:5:53: warning: overflow in implicit constant conversion
chksz.cpp:40:1: error: static assertion failed: "First::nbElementFirstEnum != Second::nbElementSecondEnum"

For reference, this original version depended on gcc printing the static_assert message even when the boolean condition doesn't compile at all.

template <typename Enum1, int Max1, typename Enum2, int Max2>
struct AssertSameSizeEnums;

template <typename Enum1, int EnumMax, typename Enum2>
struct AssertSameSizeEnums<Enum1, EnumMax, Enum2, EnumMax> {};
// only define the special case where Max1 and Max2 have the same integer value

#define ASSERT_SAME_SIZE_ENUMS(E1, M1, E2, M2) static_assert( \
    sizeof(AssertSameSizeEnums<E1, E1::M1, E2, E2::M2>), \
    #E1 "::" #M1 " != " #E2 "::" #M2 );

enum class First {
    a, b, c, nbElementFirstEnum,
};
enum class Second {
    a, b, c, nbElementSecondEnum,
};

ASSERT_SAME_SIZE_ENUMS(First, nbElementFirstEnum, Second, nbElementSecondEnum);

Note I changed your enums to be strongly-typed, because otherwise the enumerated constant names clashed. If you have weakly-typed enums, the First and Second passed to the macro should name the enclosing scope.

Now, if I comment out one of the values (so the enums are different sizes), I get:

g++ -std=c++0x -c chksz.cpp
chksz.cpp:25:113: error: invalid application of ‘sizeof’ to incomplete type ‘AssertSameSizeEnums<First, 3, Second, 2>’
chksz.cpp:25:1: error: static assertion failed: "First::nbElementFirstEnum != Second::nbElementSecondEnum"

See how the integer values are displayed in the incomplete type error, and the symbolic names in the static assertion?




回答2:


Here is the solution I found, we get a warning message with the values and the static_assert error message.

template<int N>
struct TriggerOverflowWarning
{
    static constexpr char value() { return N + 256; }
};

template <int N, int M, typename Enable = void>
struct CheckEqualityWithWarning
{
    static constexpr bool value = true;
};

template <int N, int M>
struct CheckEqualityWithWarning<N, M, typename std::enable_if<N != M>::type>
{
    static constexpr bool value = (TriggerOverflowWarning<N>::value() == TriggerOverflowWarning<M>::value());
};

static constexpr int a = 9;
static constexpr int b = 10;

static_assert(CheckEqualityWithWarning<a, b>::value, "Mismatch.");

Here is the gcc output :

g++ -std=c++11 -c test.cpp
test.cpp: In instantiation of 'static constexpr char TriggerOverflowWarning<N>::value() [with int N = 10]':
test.cpp:18:112:   required from 'constexpr const bool CheckEqualityWithWarning<9, 10>::value'
test.cpp:24:51:   required from here
test.cpp:6:52: warning: overflow in implicit constant conversion [-Woverflow]
test.cpp: In instantiation of 'static constexpr char TriggerOverflowWarning<N>::value() [with int N = 9]':
test.cpp:18:112:   required from 'constexpr const bool CheckEqualityWithWarning<9, 10>::value'
test.cpp:24:51:   required from here
test.cpp:6:52: warning: overflow in implicit constant conversion [-Woverflow]
test.cpp:24:5: error: static assertion failed: Mismatch.

It is based on this solution : Printing sizeof(T) at compile time




回答3:


First a helper class to print template argument values in compiler output:

template<size_t A, size_t B> struct TAssertEquality {
  static_assert(A==B, "Not equal");
  static constexpr bool _cResult = (A==B);
};

Then where you need to test it:

static constexpr bool _cIsEqual = 
  TAssertEquality<First::nbElementFirstEnum, Second::nbElementSecondEnum>::_cResult;

The compiler error message would look like:

note: see reference to class template instantiation 'TAssertEquality<32,64>' being compiled




回答4:


The issue with the above are that they rely on warnings which may or may not exist on different compilers, and may not be turned on by everyone all the time. (And in fact, one of them does not display values for Clang with all warnings turned on.)

This solution draws from the other solutions here, but exploits the type system, thus being an actual error all the time, according to the C++ standard. Unfortunately this does stop early and doesn't trigger the static_assert error itself, which is the one downside. This has been tested on GCC 5.3 and Clang 3.7, without turning any warnings on.

template <long V1, long V2>
struct StaticAssertEquality
{
  static constexpr void* NotEqualError() { return V1 + V2; }
};

template <long V>
struct StaticAssertEquality<V, V>
{
  static constexpr bool NotEqualError = true;
};

#define STATIC_ASSERT_LONG_EQUALITY(V1, V2)                          \
  static_assert(                                                     \
    StaticAssertEquality<static_cast<long>(V1),                      \
                         static_cast<long>(V2)>::NotEqualError,      \
    #V1 " != " #V2)

// ...

STATIC_ASSERT_LONG_EQUALITY(1, 2);

It does not work for the full range of unsigned long, obviously. As an extra safety catch, the macro could include a second straightforward static_assert(V1 == V2, #V1 " != " #V2); to catch any stray accidental equalities in the type conversion.

Output looks like this for Clang:

file.cpp: error: cannot initialize return object of type 'void *' with an rvalue of type 'long'
  static constexpr void* NotEqualError() { return V1 + V2; }
                                                  ^~~~~~~
file.cpp: note: in instantiation of member function 'StaticAssertEquality<1, 2>::NotEqualError' requested here
  STATIC_ASSERT_LONG_EQUALITY(1, 2);
  ^
file.cpp: note: expanded from macro 'STATIC_ASSERT_LONG_EQUALITY'
                              static_cast<long>(V2)>::NotEqualError,      \
                                                      ^
1 error generated.

And output with GCC:

file.cpp: In instantiation of 'static constexpr void* StaticAssertEquality<V1, V2>::NotEqualError() [with long V1 = 1; long V2 = 2]':
file.cpp:   required from here
file.cpp: error: invalid conversion from 'long' to 'void*' [-fpermissive]
   static constexpr void* NotEqualError() { return V1 + V2; }
                                                        ^
g++.exe: failed with exit code 1 (00000001)



回答5:


With C++11 and decltype:

#define UTILITY_PP_STRINGIZE_(x) #x
#define UTILITY_PP_STRINGIZE(x)  UTILITY_PP_STRINGIZE_(x)

#define STATIC_ASSERT_TRUE(exp, msg)    static_assert(::utility::StaticAssertTrue<decltype(exp), (exp)>::value, "expression: \"" UTILITY_PP_STRINGIZE(exp) "\": " msg)
#define STATIC_ASSERT_TRUE1(exp, v1, msg) \
    static_assert(::utility::StaticAssertTrue<decltype(exp), (exp), \
                  ::utility::StaticAssertParam<decltype(v1), (v1)> >::value, "expression: \"" UTILITY_PP_STRINGIZE(exp) "\": " msg)
#define STATIC_ASSERT_TRUE2(exp, v1, v2, msg) \
    static_assert(::utility::StaticAssertTrue<decltype(exp), (exp), \
                  ::utility::StaticAssertParam<decltype(v1), (v1)>, \
                  ::utility::StaticAssertParam<decltype(v2), (v2)> >::value, "expression: \"" UTILITY_PP_STRINGIZE(exp) "\": " msg)
#define STATIC_ASSERT_TRUE3(exp, v1, v2, v3, msg) \
    static_assert(::utility::StaticAssertTrue<decltype(exp), (exp), \
                  ::utility::StaticAssertParam<decltype(v1), (v1)>, \
                  ::utility::StaticAssertParam<decltype(v2), (v2)>, \
                  ::utility::StaticAssertParam<decltype(v3), (v3)> >::value, "expression: \"" UTILITY_PP_STRINGIZE(exp) "\": " msg)
#define STATIC_ASSERT_TRUE4(exp, v1, v2, v3, v4, msg) \
    static_assert(::utility::StaticAssertTrue<decltype(exp), (exp), \
                  ::utility::StaticAssertParam<decltype(v1), (v1)>, \
                  ::utility::StaticAssertParam<decltype(v2), (v2)>, \
                  ::utility::StaticAssertParam<decltype(v3), (v3)>, \
                  ::utility::StaticAssertParam<decltype(v4), (v4)> >::value, "expression: \"" UTILITY_PP_STRINGIZE(exp) "\": " msg)

#define STATIC_ASSERT_FALSE(exp, msg)   static_assert(::utility::StaticAssertFalse<decltype(exp), (exp)>::value, "expression: \"" UTILITY_PP_STRINGIZE(exp) "\": " msg)
#define STATIC_ASSERT_FALSE1(exp, v1, msg) \
    static_assert(::utility::StaticAssertFalse<decltype(exp), (exp), \
                  ::utility::StaticAssertParam<decltype(v1), (v1)> >::value, "expression: \"" UTILITY_PP_STRINGIZE(exp) "\": " msg)
#define STATIC_ASSERT_FALSE2(exp, v1, v2, msg) \
    static_assert(::utility::StaticAssertFalse<decltype(exp), (exp), \
                  ::utility::StaticAssertParam<decltype(v1), (v1)>, \
                  ::utility::StaticAssertParam<decltype(v2), (v2)> >::value, "expression: \"" UTILITY_PP_STRINGIZE(exp) "\": " msg)
#define STATIC_ASSERT_FALSE3(exp, v1, v2, v3, msg) \
    static_assert(::utility::StaticAssertFalse<decltype(exp), (exp), \
                  ::utility::StaticAssertParam<decltype(v1), (v1)>, \
                  ::utility::StaticAssertParam<decltype(v2), (v2)>, \
                  ::utility::StaticAssertParam<decltype(v3), (v3)> >::value, "expression: \"" UTILITY_PP_STRINGIZE(exp) "\": " msg)
#define STATIC_ASSERT_FALSE4(exp, v1, v2, v3, v4, msg) \
    static_assert(::utility::StaticAssertFalse<decltype(exp), (exp), \
                  ::utility::StaticAssertParam<decltype(v1), (v1)>, \
                  ::utility::StaticAssertParam<decltype(v2), (v2)>, \
                  ::utility::StaticAssertParam<decltype(v3), (v3)>, \
                  ::utility::StaticAssertParam<decltype(v4), (v4)> >::value, "expression: \"" UTILITY_PP_STRINGIZE(exp) "\": " msg)

#define STATIC_ASSERT_EQ(v1, v2, msg)   static_assert(::utility::StaticAssertEQ<decltype(v1), decltype(v2), (v1), (v2)>::value, "expression: \"" UTILITY_PP_STRINGIZE(v1) " == " UTILITY_PP_STRINGIZE(v2) "\": " msg)
#define STATIC_ASSERT_NE(v1, v2, msg)   static_assert(::utility::StaticAssertNE<decltype(v1), decltype(v2), (v1), (v2)>::value, "expression: \"" UTILITY_PP_STRINGIZE(v1) " != " UTILITY_PP_STRINGIZE(v2) "\": " msg)
#define STATIC_ASSERT_LE(v1, v2, msg)   static_assert(::utility::StaticAssertLE<decltype(v1), decltype(v2), (v1), (v2)>::value, "expression: \"" UTILITY_PP_STRINGIZE(v1) " <= " UTILITY_PP_STRINGIZE(v2) "\": " msg)
#define STATIC_ASSERT_LT(v1, v2, msg)   static_assert(::utility::StaticAssertLT<decltype(v1), decltype(v2), (v1), (v2)>::value, "expression: \"" UTILITY_PP_STRINGIZE(v1) " < "  UTILITY_PP_STRINGIZE(v2) "\": " msg)
#define STATIC_ASSERT_GE(v1, v2, msg)   static_assert(::utility::StaticAssertGE<decltype(v1), decltype(v2), (v1), (v2)>::value, "expression: \"" UTILITY_PP_STRINGIZE(v1) " >= " UTILITY_PP_STRINGIZE(v2) "\": " msg)
#define STATIC_ASSERT_GT(v1, v2, msg)   static_assert(::utility::StaticAssertGT<decltype(v1), decltype(v2), (v1), (v2)>::value, "expression: \"" UTILITY_PP_STRINGIZE(v1) " > "  UTILITY_PP_STRINGIZE(v2) "\": " msg)


namespace utility
{
    template <typename T, T v>
    struct StaticAssertParam
    {
    };

    template <typename T, T v, typename ...Params>
    struct StaticAssertTrue;

    template <typename T, T v>
    struct StaticAssertTrue<T, v>
    {
        static const bool value = (v ? true : false);
    };

    template <typename T, T v, typename ...Params>
    struct StaticAssertTrue
    {
        static const bool value = (v ? true : false);
        static_assert(v ? true : false, "StaticAssertTrue with parameters failed.");
    };

    template <typename T, T v, typename ...Params>
    struct StaticAssertFalse;

    template <typename T, T v>
    struct StaticAssertFalse<T, v>
    {
        static const bool value = (v ? false : true);
    };

    template <typename T, T v, typename ...Params>
    struct StaticAssertFalse
    {
        static const bool value = (v ? false : true);
        static_assert(v ? false : true, "StaticAssertFalse with parameters failed.");
    };

    template <typename U, typename V, U u, V v>
    struct StaticAssertEQ
    {
        static const bool value = (u == v);
        static_assert(u == v, "StaticAssertEQ failed.");
    };

    template <typename U, typename V, U u, V v>
    struct StaticAssertNE
    {
        static const bool value = (u != v);
        static_assert(u != v, "StaticAssertNE failed.");
    };

    template <typename U, typename V, U u, V v>
    struct StaticAssertLE
    {
        static const bool value = (u <= v);
        static_assert(u <= v, "StaticAssertLE failed.");
    };

    template <typename U, typename V, U u, V v>
    struct StaticAssertLT
    {
        static const bool value = (u < v);
        static_assert(u < v, "StaticAssertLT failed.");
    };

    template <typename U, typename V, U u, V v>
    struct StaticAssertGE
    {
        static const bool value = (u >= v);
        static_assert(u >= v, "StaticAssertGE failed.");
    };

    template <typename U, typename V, U u, V v>
    struct StaticAssertGT
    {
        static const bool value = (u > v);
        static_assert(u > v, "StaticAssertGT failed.");
    };
}

Usage:

struct A
{
    int a[4];
};

#define Float1 1.1f
#define Float2 1.2f

int main()
{
    static const int a = 3;
    static const long b = 5;
    static const long c = 7;
    static const long d = 9;
    STATIC_ASSERT_TRUE4(a == b && c == d, a, b, c, d, "long_expression_with_multiple_integral_variables");

    #pragma message("----------------------------------------")

    STATIC_ASSERT_TRUE(Float1 == Float2, "expression_with_floats");

    #pragma message("----------------------------------------")

    STATIC_ASSERT_EQ(10, sizeof(A), "simple_integral_expression_1");

    #pragma message("----------------------------------------")

    STATIC_ASSERT_EQ(11, sizeof(A), "simple_integral_expression_2");
    return 0;
}

MSVC2017:

source_file.cpp(72): error C2338: StaticAssertTrue with parameters failed.
source_file.cpp(148): note: see reference to class template instantiation 'utility::StaticAssertTrue<bool,false,utility::StaticAssertParam<const int,3>,utility::StaticAssertParam<const long,5>,utility::StaticAssertParam<const long,7>,utility::StaticAssertParam<const long,9>>' being compiled
source_file.cpp(148): error C2338: expression: "a == b && c == d": long_expression_with_multiple_integral_variables
----------------------------------------
source_file.cpp(152): error C2338: expression: "1.1f == 1.2f": expression_with_floats
----------------------------------------
source_file.cpp(95): error C2338: StaticAssertEQ failed.
source_file.cpp(156): note: see reference to class template instantiation 'utility::StaticAssertEQ<int,size_t,10,16>' being compiled
source_file.cpp(156): error C2338: expression: "10 == sizeof(A)": simple_integral_expression_1
----------------------------------------
source_file.cpp(160): error C2338: expression: "11 == sizeof(A)": simple_integral_expression_2

GCC 4.8.x:

<source>: In instantiation of 'struct utility::StaticAssertTrue<bool, false, utility::StaticAssertParam<const int, 3>, utility::StaticAssertParam<const long int, 5l>, utility::StaticAssertParam<const long int, 7l>, utility::StaticAssertParam<const long int, 9l> >':
<source>:148:5:   required from here
<source>:72:9: error: static assertion failed: StaticAssertTrue with parameters failed.
         static_assert(v ? true : false, "StaticAssertTrue with parameters failed.");
         ^
<source>: In function 'int main()':
<source>:18:5: error: static assertion failed: expression: "a == b && c == d": long_expression_with_multiple_integral_variables
     static_assert(::utility::StaticAssertTrue<decltype(exp), (exp), \
     ^
<source>:148:5: note: in expansion of macro 'STATIC_ASSERT_TRUE4'
     STATIC_ASSERT_TRUE4(a == b && c == d, a, b, c, d, "long_expression_with_multiple_integral_variables");
     ^
<source>:150:63: note: #pragma message: ----------------------------------------
     #pragma message("----------------------------------------")
                                                               ^
<source>:4:41: error: static assertion failed: expression: "1.1f == 1.2f": expression_with_floats
 #define STATIC_ASSERT_TRUE(exp, msg)    static_assert(::utility::StaticAssertTrue<decltype(exp), (exp)>::value, "expression: \"" UTILITY_PP_STRINGIZE(exp) "\": " msg)
                                         ^
<source>:152:5: note: in expansion of macro 'STATIC_ASSERT_TRUE'
     STATIC_ASSERT_TRUE(Float1 == Float2, "expression_with_floats");
     ^
<source>:154:63: note: #pragma message: ----------------------------------------
     #pragma message("----------------------------------------")
                                                               ^
<source>: In instantiation of 'struct utility::StaticAssertEQ<int, long unsigned int, 10, 16ul>':
<source>:156:5:   required from here
<source>:95:9: error: static assertion failed: StaticAssertEQ failed.
         static_assert(u == v, "StaticAssertEQ failed.");
         ^
<source>:44:41: error: static assertion failed: expression: "10 == sizeof(A)": simple_integral_expression_1
 #define STATIC_ASSERT_EQ(v1, v2, msg)   static_assert(::utility::StaticAssertEQ<decltype(v1), decltype(v2), (v1), (v2)>::value, "expression: \"" UTILITY_PP_STRINGIZE(v1) " == " UTILITY_PP_STRINGIZE(v2) "\": " msg)
                                         ^
<source>:156:5: note: in expansion of macro 'STATIC_ASSERT_EQ'
     STATIC_ASSERT_EQ(10, sizeof(A), "simple_integral_expression_1");
     ^
<source>:158:63: note: #pragma message: ----------------------------------------
     #pragma message("----------------------------------------")
                                                               ^
<source>: In instantiation of 'struct utility::StaticAssertEQ<int, long unsigned int, 11, 16ul>':
<source>:160:5:   required from here
<source>:95:9: error: static assertion failed: StaticAssertEQ failed.
         static_assert(u == v, "StaticAssertEQ failed.");
         ^
<source>:44:41: error: static assertion failed: expression: "11 == sizeof(A)": simple_integral_expression_2
 #define STATIC_ASSERT_EQ(v1, v2, msg)   static_assert(::utility::StaticAssertEQ<decltype(v1), decltype(v2), (v1), (v2)>::value, "expression: \"" UTILITY_PP_STRINGIZE(v1) " == " UTILITY_PP_STRINGIZE(v2) "\": " msg)
                                         ^
<source>:160:5: note: in expansion of macro 'STATIC_ASSERT_EQ'
     STATIC_ASSERT_EQ(11, sizeof(A), "simple_integral_expression_2");
     ^



回答6:


Another approach with C++11 just to lookup deeply nested type or compile time value.

#include <vector>

// generates compilation error and shows real type name (and place of declaration in some cases) in an error message, useful for debugging boost::mpl like recurrent types
#define UTILITY_TYPE_LOOKUP_BY_ERROR(type_name) \
    using _type_lookup_t = decltype((*(typename ::utility::type_lookup<type_name >::type*)0).operator ,(*(::utility::dummy*)0))

// lookup compile time template typename value
#define UTILITY_PARAM_LOOKUP_BY_ERROR(static_param) \
    UTILITY_TYPE_LOOKUP_BY_ERROR(STATIC_ASSERT_PARAM(static_param))

#define STATIC_ASSERT_PARAM(v1) ::utility::StaticAssertParam<decltype(v1), (v1)>

namespace utility
{
    template <typename T>
    struct type_lookup
    {
        using type = T;
    };

    struct dummy {};

    template <typename T, T v>
    struct StaticAssertParam
    {
    };
}

struct test
{
    char a[123];
    double b[15];
    std::vector<int> c;
};

UTILITY_PARAM_LOOKUP_BY_ERROR(sizeof(test));

MSVC2017

error C2039: ',': is not a member of 'utility::StaticAssertParam<size_t,272>'

GCC 4.8.x:

<source>:5 : 103 : error : 'using type = struct utility::StaticAssertParam<long unsigned int, 272ul>' has no member named 'operator,'
using _type_lookup_t = decltype((*(typename ::utility::type_lookup<type_name >::type*)0).operator ,(*(::utility::dummy*)0))
^
<source> : 9 : 5 : note : in expansion of macro 'UTILITY_TYPE_LOOKUP_BY_ERROR'
UTILITY_TYPE_LOOKUP_BY_ERROR(STATIC_ASSERT_PARAM(static_param))
^
<source> : 36 : 1 : note : in expansion of macro 'UTILITY_PARAM_LOOKUP_BY_ERROR'
UTILITY_PARAM_LOOKUP_BY_ERROR(sizeof(test));
^


来源:https://stackoverflow.com/questions/13837668/display-integer-at-compile-time-in-static-assert

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!