4

I have a class template which can't be used directly, only specializations are allowed. And I want to use static_assert to show meaningful error message. I can't just type static_assert(false, "error"); since false isn't value dependent and compiler may show error message even if the template is never used.

My solution:

template<class>
struct AlwaysFalse : std::false_type{};

#define DEPENDENT_FALSE(arg) AlwaysFalse<decltype(sizeof(arg))>::value

template<class T>
struct Foo{
    static_assert(DEPENDENT_FALSE(T), "You must use specialization!");
};

template<int i>
struct Bar{
    static_assert(DEPENDENT_FALSE(i), "You must use specialization!");
};

But I'm not sure about realization DEPENDENT_FALSE. Because MSVC doesn't treat sizeof(arg) as template dependent expression(unlike GCC), but decltype(sizeof(arg)) is fine.

Can somebody explain this behavior in terms of standard? Is it portable?

Barry
  • 286,269
  • 29
  • 621
  • 977
yrHeTateJlb
  • 427
  • 2
  • 20
  • Same ways you do for a class: declare its =default constructor protected, or some virtual methods pure, or a virtual destructor protected. Yes, struct can have constructors and virtual methods, though they stop you using proper c-style aggregate initialisation. The constructor is cheep. The virtual methods have a cost if you don't otherwise want virtuals. – Gem Taylor Jul 25 '18 at 16:50
  • 3
    @Gem Taylor What on earth are you babeling about? – Jesper Juhl Jul 25 '18 at 17:34

3 Answers3

6

This:

#define DEPENDENT_FALSE(arg) AlwaysFalse<decltype(sizeof(arg))>::value

fails to actually be dependent. decltype(sizeof(arg)) is always size_t, it doesn't actually depend on arg in any way (more broadly, here is a list of expressions that are never type-dependent). Since it's not dependent, a compiler is perfectly able to see that DEPENDENT_FALSE(T) is false and just trigger that static_assert.

What you want is just:

#define DEPENDENT_FALSE(arg) AlwaysFalse<decltype(arg)>::value

That is, drop the sizeof. This now is dependent.


This won't work for the int directly, since that again won't be dependent (decltype(i) is just int, and we need something value-dependent now). For that, you can just wrap it in an integral constant:

template<class T>
struct Foo{
    static_assert(AlwaysFalse<T>::value, "You must use specialization!");
};

template<int i>
struct Bar{
    static_assert(AlwaysFalse<std::integral_constant<int, i>>::value, "You must use specialization!");
};
Barry
  • 286,269
  • 29
  • 621
  • 977
  • 1
    I am uncertain this helps: so long as there is no instance of the primary specialization that makes the instantiation valid, isn't the program ill-formed? Or is a function with a static assert sufficiently valid? – Yakk - Adam Nevraumont Jul 25 '18 at 21:33
  • @Yakk-AdamNevraumont There must be a valid specialization, period. The primary doesn't have to be valid. – Barry Jul 25 '18 at 21:38
  • the standard uses specialization differently than we do. I believe each of the partial/primary/full soecializations have to have a valid instantiation. But not certain. – Yakk - Adam Nevraumont Jul 25 '18 at 23:01
  • I thought [I was told by you](https://stackoverflow.com/a/47657098/4832499) that this is in fact ill-formed NDR. Or perhaps [this](https://stackoverflow.com/a/30079285/4832499)? – Passer By Jul 26 '18 at 12:18
  • @PasserBy Neither of those answers says that. The key is that `AlwaysFalse::value` could hypothetically be specialized to be true, somewhere. But `false` and `sizeof(T) != sizeof(T)` are always `false`, neither of those depends on a template parameter. – Barry Jul 26 '18 at 13:08
  • But wouldn't it be the same if `AlwaysFalse` doesn't have any specialization that that has `true` as `value`? It is still true that no `Foo` (of the primary template) is a valid specialization. At the first point where `Foo` can possibly instantiated, if no specialization of `AlwaysFalse` up to that point has `true` as `value`, either it doesn't has one which is ill-formed NDR, or there is one after the point, which is also ill-formed NDR, isn't it? – Passer By Jul 26 '18 at 13:12
  • 1
    @PasserBy I could always, at some arbitrary point in the future, write `struct Troll { }; template <> struct AlwaysFalse : true_type { }; Foo f`. There's no "up to that point" with templates. – Barry Jul 26 '18 at 13:52
  • @PasserBy That said, it'd be great if we just had a language feature that was like `diagnostic_if_instantiated(msg);` so we wouldn't even have to worry about this. – Barry Jul 26 '18 at 13:52
-1

Maybe the following idea:

template<typename T>
struct AlwaysError
{
    AlwaysError<T>()
    {
        std::cout << T::value << std::endl;
        static_assert( T::value, "You must use specialization!");
    }
};


template <typename T>
struct Foo: public AlwaysError<Foo<T>>
{
    static constexpr bool value = false;
};

template <>
struct Foo<int>{};

int main()
{
    //Foo<double> d; // error message as expected
    Foo<int> i;  // specialized, compiles!
}

The assert message contains the text and also the type which should be specialized. In hope it helps!

Klaus
  • 24,205
  • 7
  • 58
  • 113
-1

Solution which works on C++17. Unfortunately I have only C++11

template<auto>
constexpr bool dependentFalseHelper(){
    return false;
}
template<class>
constexpr bool dependentFalseHelper(){
    return false;
}

#define DEPENDENT_FALSE(arg) (dependentFalseHelper<arg>())
yrHeTateJlb
  • 427
  • 2
  • 20