3

I'd like to use SFINAE (with void_t) to determine whether a class template specialization or instantiation has a certain member type defined. However the primary class template has a static_assert in it's body. Is it possible to retrieve this information without modifying the primary template, and without preprocessor tricks?

#include <type_traits>

template <class T>
struct X {
    static_assert(sizeof(T) < 0, "");
};

template <>
struct X<int> {
    using type = int;
};

template <class...>
using void_t = void;

template <class, class = void_t<>>
struct Has : std::false_type {};

template <class T>
struct Has<T, void_t<typename T::type>> : std::true_type {};

int main() {
    static_assert(Has<X<int>>::value == true, "");

    // How to make this compile?
    static_assert(Has<X<char>>::value == false, ""); // ERROR
}

X<int> is an explicit specialization, though X<char> is an implicit instantiation of the primary template. And when the compiler creates this instantiation then the entire SFINAE machinery is stopped with an error caused by the static_assert declaration inside the primary template body.


My concrete motivation is this: I am creating a generic wrapper class template and I'd like to define a hash function for it if it's template type parameter has a specialization for std::hash<>. However gcc 4.7 puts a static_assert inside the definition of the primary template of std::hash<>. The libstdc++ of gcc 4.8 and llvm's libc++ just simply declare the primary template. Consequently my class template does not work with gcc/libstdc++ 4.7.

// GCC 4.7.2
  /// Primary class template hash.
  template<typename _Tp>
    struct hash : public __hash_base<size_t, _Tp>
    {
      static_assert(sizeof(_Tp) < 0,
            "std::hash is not specialized for this type");
      size_t operator()(const _Tp&) const noexcept;
    };

// GCC 4.8.2
  /// Primary class template hash.
  template<typename _Tp>
    struct hash;

This problem is similar to this one, but I am not satisfied with the accepted answer there. Because here we cannot "inject 'patches' to to match the static_assert" since the assertion will always fail once the primary template is instantiated with any type parameter.

EDIT: Above I describe my concrete motivation to provide context for the abstract problem which is clearly stated upfront. This concrete motivation refers gcc 4.7, but please try to be independent from the libstdc++ 4.7 implementation details in comments and answers. This is just an example. There can be any kind of C++ libraries out there which might have a primary class template defined similarly to X.

Community
  • 1
  • 1
Gabor Marton
  • 2,039
  • 2
  • 22
  • 33
  • The primary template definition in gcc 4.7 is ill-formed NDR, so there is no way to work around it in standard C++, since any program that has that template definition is already ill-formed. See http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_closed.html#1483 – Brian Bi Dec 22 '15 at 22:10
  • 4.7 is just broken in this context, not sure if there's anything you can do about it. Upgrade to 4.8. Or 5.2. – Barry Dec 22 '15 at 22:11
  • There are other areas of the c++11 standard library where the gnu compiler suite just didn't work until recently. Regular expressions didn't work until until 4.9, localizations until 5.1. Best advice: Update your compiler. – David Hammen Dec 22 '15 at 22:25
  • @Barry @David Hammen Please try to be independent from the gcc/libstdc++ 4.7 implementation details. That is here just to provide context for the abstract problem which is clearly stated above. There can be any other C++ library which has such a class template defined similarly to `X` – Gabor Marton Dec 22 '15 at 22:45
  • 1
    You can't. Such a design is fundamentally not SFINAE-friendly. Compare [LWG 2543](http://wg21.link/LWG2543). – T.C. Dec 23 '15 at 01:31

1 Answers1

2

I really don't think so. static_assert at a class level context is meant to make sure you never instantiate a class with a type it cannot handle. It was made to avoid the far away problems created when you instantiate a class with a type that doesn't realize the expected concept but you don't find out until you make a call that uses something expected that's missing...or worse don't and then later it turns up during maintenance.

So static_assert is meant to blow up as soon as you try to instantiate the class, as you are trying to do.

There is one option you might consider though: an external class that can be used to avoid the problem. Basically a traits class that might default to some easy implementation but is overridden for X<T>. This would likely be very fragile though so I don't know that I'd introduce it into something that has a long lifetime without seriously considering other options, like changing the problem class.

Edward Strange
  • 40,307
  • 7
  • 73
  • 125