24

I want to create a class template

template <class T>
class X {
  // here I'll use T::value (among other things)
};

T::value will often be a constexpr static variable, but not always. T::value has to be positive value, so I want to let people know it during compilation, when possible.

If T::value was always constexpr, I'd add static_assert like

static_assert(T::value > 0, "need positive number");

Is it possible to add this static_assert only for cases when T::value is constexpr?

RiaD
  • 46,822
  • 11
  • 79
  • 123

3 Answers3

26

We can write an is_valid template function (come up with a better name) with two overloads:

template <typename T, int N = T::value>
constexpr bool is_valid(int) {
    return N > 0;
}

template <typename T>
constexpr bool is_valid(...) {
    return true;
}

The first overload will only be valid if T::value is a constant expression, otherwise it will be SFINAEd out. The second overload is valid no matter what, so we disambiguate the overload with a dummy int parameter.

Now we test it like so:

static_assert(is_valid<T>(0), "need positive number");

Live Demo

TartanLlama
  • 63,752
  • 13
  • 157
  • 193
  • Cool. Would be even more cool if implementation didn't have to know that it depends on T::value (i.e so that one could write `static_assert(some_func(T::value > 0), "")` as well as `static_assert(some_func(T::other_value > T::third_value()), "")` – RiaD May 19 '16 at 11:44
  • @RiaD Something like [this](http://coliru.stacked-crooked.com/a/074b7425dc9f3c18)? At that point you're essentially implementing Concepts ;). – TartanLlama May 19 '16 at 11:56
  • Well, it's not much better because still requires to create a trait for each condition, but yep, I meant something similar – RiaD May 19 '16 at 12:00
  • Ideal would be function like `return_value_if_constexpr_otherwise_true`. It would be easy to write it if there was constexpr-based overloading :) – RiaD May 19 '16 at 12:02
  • Sadly, `template constexpr auto is_constexpr(F f) -> std::enable_if_t<((void)f(),true), std::true_type>` does not seem to work (even with constexpr lambda), as you cannot use parameters before the function body starts. – Yakk - Adam Nevraumont May 19 '16 at 13:37
  • But `is_valid<>()` takes a type. What if you just have a `struct Bar{ int value; };` (not static), but use it as `constexpr Bar b = {5};`? That is `constexpr`, but you cannot call an `is_valid` on it. – Borph Jan 24 '20 at 16:01
4

This works for me on clang++:

#include <type_traits>

// The default case, returns true if the value is not constant.
template <typename T, typename = void>
struct IsNonConstantOrPositive {
    static const bool value = true;
};

// The `constexpr` case. We check if we can evaluate `T::value == 0` which can only
// be evaluated at compile-time if `T::value` is constant. The whole `enable_if` thing
// is to provide a substitution to ensure SFINAE.
template <typename T>
struct IsNonConstantOrPositive<T, typename std::enable_if<T::value==0||true>::type> {
    static const bool value = T::value > 0;
};

template <typename T>
struct X {
    static_assert(IsNonConstantOrPositive<T>::value, "T::value should be positive");
};

Example:

struct A {  // const > 0, should succeed
    static const int value = 123;
};

struct B {  // const <= 0, should fail
    static const int value = -1234;
};

struct C {   // non-const, should succeed
    static int value;
};

int main() {
    X<A> a;     // ok
    //X<B> b;   // error
    X<C> c;     // ok
}
kennytm
  • 510,854
  • 105
  • 1,084
  • 1,005
1

I think it would be nice to have a test for const(expr)-ness of a variable, to be used like:

struct T {
    ...
    static_assert(!IS_CONSTANT_VAR(value) || value > 0, "trouble is afoot");
};

The implementation below uses a similar strategy to kennytm's solution to fail out on non-constant references. It works in Clang and GCC.

#include <type_traits> // enable_if

template<typename T, T& var, typename = void> struct is_constant_var_impl {
    static constexpr bool value = false;
};
template<typename T, T& var>
struct is_constant_var_impl <T, var, typename std::enable_if<(double)var == (double)var>::type> {
    // (double) cast above to thwart GCC's agressive constant folding;
    // perhaps could be removed with a bit more finesse
    static constexpr bool value = true;
};
#define IS_CONSTANT_VAR(...) (is_constant_var_impl<decltype(__VA_ARGS__), (__VA_ARGS__)>::value)

Pros

  • Template can be reused across different classes or static member names
  • Self-explanatory code in the static_assert

Cons

  • Does not work in MSVC
  • (Maybe?) Uses C++14
  • Gross
feersum
  • 658
  • 4
  • 11
  • few comments 1) could you explain why `static_assert(!IS_CONSTANT_VAR(value) || value > 0`, "") works, considering it depends on value. Is it guaranteed? (I checked and it does on my compiler) (it's basically works same as static_assert(true || value > 0, "") which I believe shouldn't work. 2) what is from c++14, It compiled fine with std=c++11 for me (on clang) 3) I'd better use named arg for macro than VA_ARGS consifering only single argument supported – RiaD May 21 '16 at 14:52
  • @RiaD (1) The short-circuiting effect of `||` also works at compile time so it does not depend on `value` if the first argument is true. But I can't guarantee anything :) (2) Hmm, I thought I saw something about constexpr or reference parameters which required C++14, but now I can't find what it was. But it fails in g++ -std=c++11. (3) Since C++ can have expressions with unbracketed commas (from template arguments) I like to do this so it will still work with those but it's a matter of taste. – feersum May 21 '16 at 18:55