2

Is it possible to use a static_assert in conjunction with a template alias? I understand how to use SFINAE with a template alias, and how to use static_assert with a struct, but I want the static_assert with the alias to give a cleaner error message.

I have the following use case in mind:

#include <array>

constexpr bool is_valid(int n){
    return n <= 10;
}

template <int n>
struct Foo {
    static_assert(is_valid(n), "This class cannot handle more than 10 dimensions");
};

template <int n>
using Bar = std::array<float,n>;  

template <int n, std::enable_if_t<is_valid(n)> * unused = nullptr>
using BarSFINAE = std::array<float,n>;  

int main() {

    Foo<5>();
    // Foo<20>(); // Triggers the compiler-time static_assert

    Bar<5>();
    Bar<20>(); // TODO: Should trigger a compiler-time static_assert

    BarSFINAE<5>();
    // BarSFINAE<20>(); // Not allowed due to SFINAE, but throws an ugly compile time message
}

The issue is essentially that an alias doesn't have a body. So I don't know where I would even put a static_assert.

bremen_matt
  • 6,902
  • 7
  • 42
  • 90
  • I'm curious why the `static_assert` would only apply to an alias. That's a very thin layer of security over someone just using the aliased type/template. Either you have control over the aliased type/template (in that case just do the check there?) or you don't (in that case you can't easily prevent others from ignoring your alias)... – Max Langhof Jul 10 '19 at 08:55
  • Why not directly `template using Bar = std::enable_if_t>`? BTW, non-type template parameters shall not have type `void*` in C++14. – L. F. Jul 10 '19 at 09:04
  • @MaxLanghof We are compiling a library for distribution. Obviously the user can just modify this header and remove the static assert, but then they will get bizarre linker errors because the library was only compiled for specific conditions – bremen_matt Jul 10 '19 at 09:08
  • @L.F. I don't get your point about the void* – bremen_matt Jul 10 '19 at 09:12
  • @bremen_matt Use another type, like `int`. – L. F. Jul 10 '19 at 09:13
  • @bremen_matt See https://stackoverflow.com/q/56003162. – L. F. Jul 10 '19 at 09:14
  • By "ignoring the alias" I meant "use the aliased type directly", not "edit the header file". – Max Langhof Jul 10 '19 at 09:20
  • They can certainly do that. But the point is that if they aren't careful and don't use the alias, then it would be easy to write code for which the library was not compiled. So they would get nasty linker errors. – bremen_matt Jul 10 '19 at 09:31
  • @bremen_matt The point is that you could make this just straight up impossible (to do by accident) by simply using a strong type rather than an alias. – Cubic Jul 10 '19 at 13:36
  • That is true. The downside is interoperability. Suppose I want a library to work with std::vectors of certain types. I could just create a type that (gasp) inherits from std::vector, but the issue there is that you would have to write the boiler plate to copy from and to your class – bremen_matt Jul 10 '19 at 13:59
  • But there is really no need for this boiler plate. – bremen_matt Jul 10 '19 at 13:59

2 Answers2

5

As you correctly identified the issue, the solution is to add something with a body that the alias can depend on. For instance, a function.

namespace detail {
    template<std::size_t N>
    constexpr auto checked_size() {
        static_assert(is_valid(N), "");
        return N;
    }
}

template <int n>
using Bar = std::array<float, detail::checked_size<n>()>;  
StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
  • 1
    `std::enable_if` pretty much does this for us. `template using Bar = std::enable_if_t(), std::array>;` would be effectively the same. – L. F. Jul 10 '19 at 09:21
  • @L.F. - Bar the custom error message, which is what the OP is after I think. enable_if will just leave you with a perplexing error that something is not defined. – StoryTeller - Unslander Monica Jul 10 '19 at 09:36
2

Yes, you can do it by using an auxiliary struct like so:

template<typename T>
struct A {};

template<int n, typename T>
struct B_impl {
  static_assert(n <= 10,
      "this is a demonstration");
  using type = A<T>;
};

template<int n, typename T>
using B = typename B_impl<n, T>::type;

using B_good = B<3, int>;

using B_bad = B<11, int>;

int main() {
  B_good good; // works
  B_bad bad; // static assertion failure
}
Cubic
  • 14,902
  • 5
  • 47
  • 92