Home > other >  How to convert C 17's "if constexpr(std::is_literal_type<T>::value)" to C 11
How to convert C 17's "if constexpr(std::is_literal_type<T>::value)" to C 11

Time:11-22

Currently, I have this templated function in my codebase, which works pretty well in C 17:

/** This function returns a reference to a read-only, default-constructed
  * static singleton object of type T.
  */
template <typename T> const T & GetDefaultObjectForType()
{
   if constexpr (std::is_literal_type<T>::value)
   {
      static constexpr T _defaultObject = T();
      return _defaultObject;
   }
   else
   {
      static const T _defaultObject;
      return _defaultObject;
   }
}

The function has two problems, though:

  1. It uses if constexpr, which means it won't compile under C 11 or C 14.
  2. It uses std::is_literal_type, which is deprecated in C 17 and removed in C 20.

To avoid these problems, I'd like to rewrite this functionality to use the classic C 11-compatible SFINAE approach instead. The desired behavior is that for types that are constexpr-constructible (e.g. int, float, const char *), the constexpr method will be called, and for types that are not (e.g. std::string), the non-constexpr method will be called.

Here's what I've come up with so far (based on the example shown in this answer); it compiles, but it doesn't work as desired:

#include <string>
#include <type_traits>

namespace ugly_constexpr_sfinae_details
{
   template<int> struct sfinae_true : std::true_type{};

   template<class T> sfinae_true<(T::T(), 0)> is_constexpr(int);
   template<class> std::false_type is_constexpr(...);
   template<class T> struct has_constexpr_f : decltype(is_constexpr<T>(0)){};

   // constexpr version
   template<typename T, typename std::enable_if<true == has_constexpr_f<T>::value, T>::type* = nullptr> const T & GetDefaultObjectForType()
   {
      printf("constexpr method called!\n");

      static constexpr T _defaultObject = T();
      return _defaultObject;
   }

   // non-constexpr version
   template<typename T, typename std::enable_if<false == has_constexpr_f<T>::value, T>::type* = nullptr> const T & GetDefaultObjectForType()
   {
      printf("const method called!\n");

      static const T _defaultObject = T();
      return _defaultObject;
   }
}

/** Returns a read-only reference to a default-constructed singleton object of the given type */
template<typename T> const T & GetDefaultObjectForType()
{
   return ugly_constexpr_sfinae_details::GetDefaultObjectForType<T>();
}

int main(int, char **)
{
   const int         & defaultInt    = GetDefaultObjectForType<int>();          // should call the constexpr     function in the namespace
   const float       & defaultFloat  = GetDefaultObjectForType<float>();        // should call the constexpr     function in the namespace
   const std::string & defaultString = GetDefaultObjectForType<std::string>();  // should call the non-constexpr function in the namespace

   return 0;
}

When I run the program above, here is the output I see it print to stdout:

$ ./a.out 
const method called!
const method called!
const method called!

... but the output I would like it to emit is this:

$ ./a.out 
constexpr method called!
constexpr method called!
const method called!

Can anyone point out what I'm doing wrong? (I apologize if it's something obvious; SFINAE logic is not a concept that comes naturally to me :/ )

CodePudding user response:

As @Igor Tandetnik mentions in comments, static const T _defaultObject{}; works in both cases and performs compile-time initialization when possible. There's no need for constexpr.

N3337 [basic.start.init]:

Constant initialization is performed:

  • [...]
  • if an object with static or thread storage duration is initialized by a constructor call, if the constructor is a constexpr constructor, if all constructor arguments are constant expressions (including conversions), and if, after function invocation substitution ([dcl.constexpr]), every constructor call and full-expression in the mem-initializers and in the brace-or-equal-initializers for non-static data members is a constant expression;
  • if an object with static or thread storage duration is not initialized by a constructor call and if every full-expression that appears in its initializer is a constant expression.
  • Related