What is the best way to express a templated numeri

2019-07-01 09:54发布

问题:

I have a function that could be reduced to something like this:

template<class T>
T foo(T x)
{
  return 123.45 / x;
}

I would like to ensure that the numeric literal 123.45 is the same type as x. Let's say that T could be any numeric type: signed/unsigned char to long-long or float to long-double.

What is the most modern way to indicate that 123.45 should be of type T?

My requirements

  • No warnings should be emitted (with every warning turned on).
  • I want the decimal number 123.45 to have the precision of T when used in the calculation.
  • I want the most elegant solution that achieves these goals.

Issues under consideration

  • The “old-style cast”, i.e. (T)123.45 is essentially deprecated in C++.
  • static_cast<T>(123.45) seems like the right type of cast. However, it is verbose, and I have never seen this used on a literal.
  • I am unsure if a suffix (e.g. 123.45L) is necessary in order to get the maximum precision in the case of T being long double, or if the compiler will automatically use the highest precision decimal-to-binary conversion.

Update 5-7-2014

After changing hundreds of casts in my codebase to the form T(123.45), I found that there are times when this syntax is unavailable. For example, you can not do long double(123.45) because C++ does not support multitoken type constructors. In these cases, I have opted for static_cast<long double>(123.45).

回答1:

What about a simple

template<class T>
T foo(T x)
{
  return T(123.45) / x;
}

which will allow custom numeric classes to accept the value in a constructor.



回答2:

static_cast is the way to go. This is what the C cast is equivalent to, in any cast that preserves the original meaning but changes the type of the value.

If it's a class type with numeric features, static_cast will call a one-argument constructor, even an explicit one.

If you want to avoid narrowing conversions, you can use the syntax T{ 123.45 }. This is not like a C-style cast, but uses direct-list-initialization. It still allows an explicit constructor, but any numeric conversion to its parameter type must be exact. (No overflow or rounding allowed. For literals, this constraint is checked for the particular value, not as a relationship between types. However, GCC warns when I pass 12.0 to an int; I'm not sure if that's right or not.)



回答3:

The static cast is natural. If you haven't seen it, well, not everyone sees everything that happens :-) I tend to use C-style casts when (a) I know both types involved, (b) I'm entitled to be a bit lazy. As you'll see in the code below.

You do need the L suffix if you want the precision when T is more precise than double. Potentially static_cast<long double>(123.45) != 123.45L.

Suppose that T was some even more precise type, like mpf_class from GMP or some compiler-specific extended type beyond long double. You might want to consider boost::lexical_cast<T>("123.45"). Of course that won't work for integer types, you'd need two separate cases with enable_if or whatever.

Also note that if T is an integer type of lower rank than int, then for example short(123.45) / x is the same as int(123.45) / x because the division is performed in int either way and short(123.45) == int(123.45). And if T is an integer type of higher rank than int then for example long(123.45) / x is still the same as int(123.45) / x because the division will be performed in type T either way and int(123.45) == long(123.45). This analysis relies on the fact that 123 has the same value in any integer type[*]. If you could get wraparound converting 123.45 to T then things could be different. For example -1 / (unsigned char)2 != (unsigned char)-1 / (unsigned char)2.

[*] I'm ignoring bool, I can't actually remember whether it's a numeric type or not.



回答4:

You may also try to do it in the following way:

// RealConstant.h

template <typename Real>
class RealConstant
{
public:

    static 
    const Real value;

};

#define REAL_CONSTANT_DECLARATION(Real) \
template <> \
const Real RealConstant<Real>::value;

REAL_CONSTANT_DECLARATION(float)
REAL_CONSTANT_DECLARATION(double)
REAL_CONSTANT_DECLARATION(long double)

#undef REAL_CONSTANT_DECLARATION


// RealConstant.cpp

#include "RealConstant.h"

#define REAL_CONSTANT_DEFINITION(Real, constantValue) \
template <> \
const Real RealConstant<Real>::value = constantValue;

REAL_CONSTANT_DEFINITION(float, 123.45f)
REAL_CONSTANT_DEFINITION(double, 123.45)
REAL_CONSTANT_DEFINITION(long double, 123.45L)

#undef REAL_CONSTANT_DEFINTION


// File in which `foo` function template is defined

#include "RealConstant.h"

template<class T>
T foo(T x)
{
    return RealConstant<T>::value / x;
}

Macros are not really needed, of course.



回答5:

I would initialize a constant T from the literal:

template<class T>
T foo(T x)
{
  const T magic = 123.45;
  return magic / x;
}

it's simultaneously more explicit and more readable than the static_cast, and more importantly gives you the opportunity to document the magic number with a proper name ;)