Static_Assert Fails Compilation Even Though Template Function Is Called Nowhere

static_assert fails compilation even though template function is called nowhere

That's because the condition does not depend in any way on the template parameters. Therefore, the compiler can evaluate it even before instantiating that template, and produces the associated compilation error message if it the evaluation yields false.

In other words, this is not a bug. Although many things can only be checked once a template is instantiated, there are other validity checks that a compiler can perform even before. This is why C++ has a two-phase name lookup, for instance. The compiler is just trying to help you finding errors that are 100% likely to occur.

static_assert causing program to not compile even though the assert is in the header of a function template

it seems as if my substitution failure is not an error is not correctly working.

The rule is "Substitution Failure Is Not An Error". However, this only applies to the process of finding a type.

What's happening here is that finding a matching type actually succeeds, but it lands on a type that happens to have a failing static_assert(), which causes a compilation error.

There never was any substitution failure to consider as "not an error".

For your ibounded type to be used in SFINAE, it needs to not exist at all when the condition doesn't pass.

Since you are trying to wrap your head around this, I think it's worth going over what it would take for ibounded to work. However, there's a way easier way to do this in practice, see below for details.

Manual SFINAE: (for your curiosity)

// First declare ibounded without defining it
template<int lower,
int upper,
int val,
typename rtype,
typename pass=std::true_type> // <---- magic sauce here
struct ibounded;


// Add a partial specialization for which the pass parameter will be set to true or false depending on the condition.
template<int lower, int upper, int val, typename rtype>
struct ibounded<lower,
upper,
val,
rtype,
std::integral_constant<bool, (lower < val && upper > val)>> {
using TYPE = rtype;
};

The key point here is having a default value for the pass parameter in the original declaration. Because of it, any attempt to use the ibounded<a, b, c, T> type is actually trying to use ibounded<a, b, c, T, std::true_type>.

Now, onto the partial specialization. It defines the following types:

  • ibounded<a, b, c, void, std::true_type>, but only when a < c < b
  • ibounded<a, b, c, void, std::false_type>, but only when a >= c >= b

There doesn't exist a definition of ibounded<a, b, c, T, std::true_type> for which c is not between a and b. All that's left to do is to make sure that you never manually set the value of the pass parameter, and ibounded is good to go as a SFINAE test.

Easier way:
std::enable_if_t<bool, T> is a type that exists as T if the bool is true, and does not exist otherwise. So you can just use:

template<int lower, int upper, int val, typename rtype>
using ibounded = std::enable_if_t<(lower < val && upper > val), rtype>;

It becoming rtype makes using it a lot nicer as well, since you don't have to lookup the type:

template<int filenum_t, int pos> inline auto _init_filename() -> ibounded<0, 9, filenum_t, void> {

Is this use of static_assert inside if constexpr well-formed?

Both of your attempts (with the function and with the struct) are well-formed as is.

The other answer mentions [temp.res]/8, but I disagree with how it was interpreted.

The validity of a template may be checked prior to any instantiation. ... The program is ill-formed, no diagnostic required, if:

— no valid specialization can be generated for a template or a substatement of a constexpr if statement within a template and the template is not instantiated, or ...

Both the function and struct you wrote can be specialized to be true. I believe the mere possibility of specialization is enough, you don't actually need to add a dummy true specialization to make the code well-formed.

According to my understanding (and according to common sense, I hope), the point of this part of the standard is to allow compilers to check validity of templates and if constexpr branches early (when they are seen for the first time), and reject the ones that can't possibly be instantiated.

Every branch in your template can potentially be instantiated, because bool_value() can be specialized later. I doubt a sane compiler is going to reject your code due to bool_value() not being specialized yet.

Use static_assert to ensure template parameter is only used *at most* once

You can solve this problem with a macro:

#define GPIO(Port, Pin) \
friend void _gpio_ ## Port ## _ ## Pin(){} \
Gpio<Port, Pin>

Then if you use GPIO(2, 2) twice, the compiler will say something like this:

error: redefinition of '_gpio_2_2'
GPIO(2, 2) ledX;
^

<source>:14:3: note: previous definition is here
GPIO(2, 2) ledB;
^

Demo: https://godbolt.org/z/ronV0u

Why do std classes not use static_assert on non-copyable types?

  1. The reason of cryptic messages is a problem of compiler, not C++, although that's true that for C++ implementing good messages is harder.
    Clang provides much better error messages than other compilers, for
    example.

  2. I don't know why MS decided to show private overloads in
    VS intellisense - but that's definitely not a С++ problem but
    a problem of IDE (or there is some hidden, unknown for me sense).

  3. static_assert is supported only in C++11 and it
    would require to change even more standard specs just to change
    error message.

  4. Private constructor is more idiomatic in C++ than
    custom static_asserts.

  5. This suggestion doesn't even make any sense. The static_assert will cause a compiler error. Always. Whether anyone tries to call the copy constructor or not. (As pointed out by @BenjaminLindley in comments)

static_assert in production code header: bad for compilation time?

If I keep the static_assert in the production code header, will they be run for every compilation unit I include the header in?

Yes. An include does nothing else then inserting the content of the included file into the including file. To the C++ compiler there's no difference whether your code comes from a header or not. The static_assert will therefore be always evaluated.

Or is the compiler somehow 'smart' about static_assert statements?

It could be. But most compilers are probably not very smart about it, since the focus of compiler development has traditionally been fast executables, not evaluating programs fast at compilation-time.

But what about performance (compilation time) if we had lots of tests?

Well, the compiler will have to execute your code. This will probably be slower than compiling and running the respective code in absolute time, but asymptotically as fast.

You maybe interested in this discussion: https://lists.llvm.org/pipermail/cfe-dev/2019-July/062799.html

As a practical recommendation: Since you are apparently using static_asserts to unit test your code, I would recommend to remove them from the header. Tests should always be separated from the tested code.



Related Topics



Leave a reply



Submit