22

Is it possible to check with SFINAE if the type is completely defined?

E.g.

template <class T> struct hash;
template <>        struct hash<int> {};

// is_defined_hash_type definition...

enum Enum { A, B, C, D };

static_assert (  is_defined_hash_type<int> ::value, "hash<int> should be defined");
static_assert (! is_defined_hash_type<Enum>::value, "hash<Enum> should not be defined");

The solution should not modify the hash struct.

Nikki Chumakov
  • 1,215
  • 8
  • 18
  • Related: https://stackoverflow.com/questions/68573739/how-to-check-that-a-c-class-is-incomplete-only-declared – Fedor Jul 29 '21 at 14:51

3 Answers3

20

You can make an is_complete type trait, using the fact that it is ill-formed to evaluate sizeof(T) for an incomplete type T:

template <typename T>
struct is_complete_helper {
    template <typename U>
    static auto test(U*)  -> std::integral_constant<bool, sizeof(U) == sizeof(U)>;
    static auto test(...) -> std::false_type;
    using type = decltype(test((T*)0));
};

template <typename T>
struct is_complete : is_complete_helper<T>::type {};

and use it to check for is_defined_hash_type<T> by determining if hash<T> is complete. (Live at Coliru)

As Daniel says in his answer, the utility of such a thing is limited. The trait doesn't actually test if the type is complete at the point in the code where you query, it tests if the type was complete at the point in the program where the trait was first instantiated for a given type.

Casey
  • 41,449
  • 7
  • 95
  • 125
  • 1
    This doesn't work for function references – 김선달 Jul 12 '21 at 15:53
  • @김선달 very true. As formulated, this only works for object types. I'd clean up the answer, but as the question is closed as a duplicate it doesn't seem worth the trouble. – Casey Jul 13 '21 at 16:41
  • _it tests if the type was complete at the point in the program where the trait was first instantiated for a given type_ You can add the second template parameter like in [this answer](https://stackoverflow.com/a/1956217/5447906). If it is different each time the template is instantiated, then checking if type is complete will be done again without reusing previous instantiation. – anton_rh Jul 25 '21 at 14:26
6

It's not possible. The reason is that you'd have to define is_defined_hash_type<T> but there can be only one definition. But if you later define T, the definition of is_defined_hash_type<T> would yield a different result, hence a different definition, and that's not allowed. It's a violation of the ODR (One definition rule).

Daniel Frey
  • 55,810
  • 13
  • 122
  • 180
  • @Orient As Casey said in his answer, that trait is quite broken by design. This is what I explained in my answer - your program will be illegal and even if it seems to work, it may change its meaning in subtle and unpredictable ways. – Daniel Frey Sep 17 '14 at 15:37
  • 1
    I think it doesn't constitute an issue, if the instantiation is local to a TU and has no external linkage (trivially arranged). But it is certainly something to keep in mind. Also, this could be mitigated by adding a tag type: `is_defined_hash_type` – sehe Oct 16 '14 at 21:57
  • It is possible: https://stackoverflow.com/a/1956217/5447906 – anton_rh Jul 25 '21 at 15:09
  • 1
    @anton_rh There is no solution within the limits of the C++ standard, `__COUNTER__` is non-standard. – Daniel Frey Jul 25 '21 at 15:36
  • But `__LINE__` is standard. It can be used instead of `__COUNTER__`. – anton_rh Jul 25 '21 at 16:42
  • Then it won‘t work in certain macros. – Daniel Frey Jul 25 '21 at 18:30
  • @DanielFrey __COUNTER__ can be replaced by BOOST_PP_COUNTER. So, at the end of the day, counter can be expressed by standard C++ features. – Nikki Chumakov Jul 31 '21 at 14:59
  • 2
    @NikkiChumakov It requires `#include BOOST_PP_UPDATE_COUNTER()` to increase the value, which can not be part of the `IS_COMPLETE` macro, so no, it is still not possible in a portable, standard-conforming way. – Daniel Frey Jul 31 '21 at 15:28
  • @NikkiChumakov No, there is (currently) no standard-conform counter possibility within the C++ standard, and surely no "external linkaged" one, that would be really required here for all relevant scenarios. And as of the surely extended module approach since C++20, I guess there will never be one. – Secundi Sep 16 '21 at 12:15
0

The best I came up with so far is the following, which requires at least a typedef with a common name in all the specialization of hash:

template <class T> struct hash;
template <>        struct hash<int> {
    typedef int value_type;
    };

template<class T>
constexpr bool is_defined_hash_type(typename hash<T>::value_type) {
  return true;
}

template<class T>
constexpr bool is_defined_hash_type(T) {
  return false;
}

int main()
{
  static_assert (  is_defined_hash_type< int >(0), "hash<int> should be defined");
  static_assert (! is_defined_hash_type< double>(0), "hash<Enum> should not be defined");
  return 0;
}

The syntax is pretty ugly, due to the added parameter (needed to trigger SFINAE). If you think it may be the way to go, I'll try to clean it up further.

Disclaimer: I am by no means a C++11 expert, so I may have missed some points using new features. In that case fire at will and I'll try to correct the answer.

Massimiliano
  • 7,842
  • 2
  • 47
  • 62