"has_trivial_destructor" defined instead of "is_trivially_destructible"
Asked Answered
P

3

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?

Puzzler answered 3/10, 2012 at 4:53 Comment(2)
Using a predefined macro like __GNUC_MINOR__ might make it a little easier.Escargot
@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: #257788Puzzler
S
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.

Swelling answered 3/10, 2012 at 20:56 Comment(5)
Ahh, didn't see you post an answer. :( After reading this, it seems to be the obvious route, dang.Floating
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? :-)Puzzler
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 open-std.org/jtc1/sc22/wg21/docs/papers/2010/n3142.html for moreSwelling
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<T>(0)) type;.Succinylsulfathiazole
@KennyTM, hmm, maybe it's not valid then, I can't check libc++, sorrySwelling
F
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.

Floating answered 3/10, 2012 at 20:59 Comment(2)
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?Puzzler
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.Swelling
G
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" );
}
Gilda answered 14/10, 2015 at 11:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.