FrozenHeart
FrozenHeart

Reputation: 20756

How to avoid narrowing conversion with floating point literals inside template functions

What do you usually do with floating point literals inside template functions?

Suppose that we have the following code:

template <typename T>
struct Foo
{
    T bar;
};

template <typename T>
void foo(T val)
{
    Foo<T> instance = { val * 5.0 };
}

int main()
{
    foo(0.0f);
}

http://coliru.stacked-crooked.com/a/b159117097cc2461

It gives me the following warning / error (depending on the compiler / flags):

narrowing conversion of '(((double)val) * 5.0e+0)' from 'double' to 'float' inside { } [-Wnarrowing]
     Foo<T> instance = { val * 5.0 };

How is it usually avoided? Should I just wrap any floating point literal in T(0.0) like:

Foo<T> instance = { val * T(5.0) };

Or is there any more elegant solution?

Upvotes: 4

Views: 387

Answers (3)

Massimiliano Janes
Massimiliano Janes

Reputation: 5624

How is it usually avoided?

I don’t think you should ‘avoid’ it as such, I’d say it depends on the final function semantics.

That is, if you want to disallow narrowing you use {} and let the compilation fail ( at least, as of >=c++11 ); if you want to allow narrowing to T on the final result you write instance = T( val * 5.0 ) and let converions do the rest for each possible T; if you want to allow narrowing to T in intermidiate results or constants you wrap in T() there; if you want the user to decide, you add a trait/policy class performing the final/intermidiate conversions …

that said, on one thing I'm sure: as a user of your function, I'd be very uncomfortable if any of the possibilities above were undocumented, or unreasonable given the function ultimate goal...

BTW, if you're question focuses only on constants, then I'd follow boost approach, maybe with the aid of c++14's variable templates.

Upvotes: 1

ComicSansMS
ComicSansMS

Reputation: 54679

If you are only dealing with a small number of well-defined magic constants but you need full control over the value of those constants for all possible Ts, it might make sense moving them to a traits class:

template<typename T>
struct FooTraits;

template<>
struct FooTraits<float> { static constexpr float magicN() { return 5.0f; } };

template<>
struct FooTraits<double> { static constexpr double magicN() { return 5.0; } };

template <typename T>
void foo(T val)
{
     Foo<T> instance = { val * FooTraits<T>::magicN() };
}

This is some significant overhead in terms of boilerplate, so only do this if you actually need the added flexibility. If a simple cast to T works for all your use cases, there's nothing wrong with that.

Upvotes: 1

user6357139
user6357139

Reputation:

I would argue that T(5.0) is the most elegant solution. If T is a double then the conversion should be ignored by the compiler to which it is the same as writing 5.0. If T is a float then the conversion should resolved at compile time being equivalent writing as a float literal 5.0f.

Upvotes: 1

Related Questions