7

During the refinement process of the C++11 standard, it seems that is_trivially_destructible was considered a better/more-consistent name than has_trivial_destructor.

This is a relatively recent development, as my g++ 4.7.1 still uses the old name, and it's been fixed to be compliant with the standard as of 4.8:

http://gcc.gnu.org/bugzilla/show_bug.cgi?id=52702

I've been lazily using an #if which favors the compiler I'm on:

#if TRIVIAL_DESTRUCTOR_TYPE_TRAIT_MATCHES_STANDARD
template<class T>
using is_trivially_destructible = std::is_trivially_destructible<T>;
#else
template<class T>
using is_trivially_destructible = std::has_trivial_destructor<T>;
#endif

...but now I'm trying to share the source with 4.8 users and other compilers chasing the standard. Is there any better trick for making the detection of the situation more "automatic" and not require a #define?

  • 2
    Using a predefined macro like `__GNUC_MINOR__` might make it a little easier. – Vaughn Cato Oct 03 '12 at 05:08
  • @VaughnCato Yup, it's definitely a step forward, but it seems MSVC and others are coming into compliance on their own terms. Could let an #if tree evolve to encompass the set of users, but was hoping for an analogue to this: http://stackoverflow.com/questions/257288/is-it-possible-to-write-a-c-template-to-check-for-a-functions-existence – HostileFork says dont trust SE Oct 03 '12 at 05:11

3 Answers3

10

This works for me with GCC 4.7 and 4.8, correctly telling me whether the old or new trait is provided:

#include <type_traits>

namespace std
{
  template<typename> struct has_trivial_destructor;
  template<typename> struct is_trivially_destructible;
}

template<typename T>
  class have_cxx11_trait_helper
  {
    template<typename T2, bool = std::is_trivially_destructible<T2>::type::value>
      static std::true_type test(int);

    template<typename T2, bool = std::has_trivial_destructor<T2>::type::value>
      static std::false_type test(...);

  public:
    typedef decltype(test<T>(0)) type;
  };

template<typename T>
  struct have_cxx11_trait : have_cxx11_trait_helper<T>::type
  { };

int main()
{
  static_assert( have_cxx11_trait<int>::value, "new trait" );
}

N.B. I declare (but don't define) both traits because the standard library (probably) won't declare both and if the name isn't even declared then you can't refer to std::is_trivially_destructible. So I declare them both, but only the one defined by the library will be usable. Adding declarations to namespace std is technically undefined behaviour, so use it at your own risk (it's not likely to wipe your hard drive in this case though.)

Unfortunately an older compiler that doesn't provide the new trait might not be able to handle the code either -- I haven't checked if it works with GCC 4.6

Now you can define your own portable trait:

template<typename T>
  using is_trivially_destructible
    = typename std::conditional<have_cxx11_trait<T>::value,
                                std::is_trivially_destructible<T>,
                                std::has_trivial_destructor<T>>::type;

The semantics of has_trivial_destructor aren't the same as the new trait, but it's a reasonable approximation for older compilers that don't support the new trait.

Alternatively, you could use static polymoprhism to get different code depending on which type trait is available, e.g. by specializing templates or by overloading and tag dispatching, like so:

template<typename T>
  void foo_helper(const T&, std::true_type)
  {
    // code that uses std::is_trivially_destructible
  }

template<typename T>
  void foo_helper(const T&, std::false_type)
  {
    // different code using std::has_trivial_destructor
  }

template<typename T>
  void foo(const T& t)
  {
    // do common stuff

    // stuff that depends on trait
    foo_helper(t, has_cxx11_trait<T>{});

    // more common stuff
  }

No macros were harmed in the making of this answer.

kennytm
  • 510,854
  • 105
  • 1,084
  • 1,005
Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521
  • Ahh, didn't see you post an answer. :( After reading this, it seems to be the obvious route, dang. – Xeo Oct 03 '12 at 21:00
  • Yup, that's the sort of thing I was looking for! Is it easy to explain the semantic difference between `has_trivial_destructor` and `is_trivially_destructible` or should that be a new question? :-) – HostileFork says dont trust SE Oct 03 '12 at 21:42
  • 1
    Actually for those traits there may not be any meaningful difference. For some of the _constructible_ traits there are subtle differences between the old and new forms, see http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/n3142.html for more – Jonathan Wakely Oct 03 '12 at 22:01
  • I can't get this to work with clang++ 3.1 with libc++ (which has the is_trivially_destructible traits). It fails at the line `typedef decltype(test(0)) type;`. – kennytm Oct 19 '12 at 12:21
  • @KennyTM, hmm, maybe it's not valid then, I can't check libc++, sorry – Jonathan Wakely Oct 19 '12 at 12:35
4

Here's a very hackish and officially UB snippet that can test whether the std namespace sports the has_trivial_destructor name and has a trivially_destructible trait alias pick up the right trait to check (is_trivially_destructible in the case has_trivial_destructor is not available).

#include <type_traits>

template<class>
struct has_trivial_destructor{ using test_fail = int; };

template<class>
struct is_trivially_destructible{ using test_fail = int; };

// very hackish and officially UB
namespace std{
  template<class T>
  struct inherit_htd : has_trivial_destructor<T>{};
  template<class T>
  struct inherit_itd : is_trivially_destructible<T>{};
}

namespace check_htd{
  template<class T>
  struct sfinae_false : ::std::false_type{};

  template<class T>
  auto test(int) -> sfinae_false<typename ::std::inherit_htd<T>::test_fail>;
  template<class>
  auto test(...) -> ::std::true_type;

  struct htd_available : decltype(test<int>(0)){};
}

template<class T>
using Apply = typename T::type;

template<class C, class T, class F>
using If = Apply<std::conditional<C::value,T,F>>;

template<class T>
using trivially_destructible = If<check_htd::htd_available, std::inherit_htd<T>, std::inherit_itd<T>>;

Live example.

Xeo
  • 129,499
  • 52
  • 291
  • 397
  • Hackish that works, works! But as ideal solution wouldn't put anything new into the std:: namespace...yet both your solution and the one by @JonathanWakely did so...is there a fundamental argument for understanding why that's necessary to achieve this? – HostileFork says dont trust SE Oct 03 '12 at 21:45
  • Because it's ill-formed to refer to a qualified name `A::foo` if `foo` hasn't already been declared in that scope. SFINAE doesn't apply because `A` is not a template so there's no substitution of template arguments taking place. – Jonathan Wakely Oct 03 '12 at 21:58
2

I have similar issues, and previously checked for GCC Version Macros (unfortunately there is no way to check for the proper libstd++ version, just a datecode is available). The Previous Answer by Jonathan Wakely is a good solution, but fails with libc++ and possibly other libraries that define the templates in a versioned inline namespace, or map that namespace into std via using. That way the prototypes wont fit.

To make the code from Jonathan Wakely fit, you need to check for libc++ and define the correct namespace.

#include <type_traits>


#ifdef _LIBCPP_BEGIN_NAMESPACE_STD
_LIBCPP_BEGIN_NAMESPACE_STD
#else
namespace std {
#endif
  template<typename> struct has_trivial_destructor;
  template<typename> struct is_trivially_destructible;
// All unimplemented in gcc 4.9
  template<typename, typename...> struct is_trivially_constructible;
  template<typename> struct is_trivially_default_constructible;
  template<typename> struct is_trivially_copy_constructible;
  template<typename> struct is_trivially_move_constructible;
  template<typename> struct is_trivially_assignable;
  template<typename> struct is_trivially_copy_assignable;
  template<typename> struct is_trivially_move_assignable;
  template<typename> struct is_trivially_copyable;
#ifdef _LIBCPP_BEGIN_NAMESPACE_STD
_LIBCPP_END_NAMESPACE_STD
#else
} // namespace std
#endif

template<typename T>
  class have_cxx11_trait_helper
  {
    template<typename T2, bool =  std::is_trivially_destructible<T2>::type::value>
      static std::true_type test(int);

    template<typename T2, bool =  std::has_trivial_destructor<T2>::type::value>
      static std::false_type test(...);

  public:
    typedef decltype(test<T>(0)) type;
  };

template<typename T>
  struct have_cxx11_trait : have_cxx11_trait_helper<T>::type
  { };

int main()
{
 static_assert( have_cxx11_trait<int>::value, "new trait" );
}
Community
  • 1
  • 1
Norbert Lange
  • 1,162
  • 1
  • 10
  • 15