Max Ehrlich Max Ehrlich - 1 month ago 9
C++ Question

Why does this code fail to compile?

I know it's a pretty general title, but I have some code and it strikes me as weird that it cant compile.

Here is a demo of the issue. If you change

scalar_t
from
double
to
float
the code compiles fine. Why cant float be promoted to double here? In fact, if you change the constants to
double
s (
1.0
) or
int
s (
1
) they also cant be promoted. Isn't this the kind of thing that should just work?

Full code sample:

#include <valarray>
#include <numeric>
#include <iterator>
#include <iostream>

template<typename T>
T sigmoid(const T &in)
{
return 1.f / (1.f + std::exp(-in));
}

template<typename T>
T logit(const T &in)
{
return std::log(in / (1.f - in));
}

using scalar_t = double;

int main(int argc, char **argv)
{
std::valarray<scalar_t> f = { 0.1f, 0.3f, 0.5f, 0.9f };

scalar_t alpha = 0.5f;
scalar_t beta = -1.f;

auto lC = logit(f);
std::valarray<scalar_t> skC = alpha * lC + beta;
auto sC = sigmoid(skC);

std::copy(std::begin(sC), std::end(sC), std::ostream_iterator<scalar_t>(std::cout, " "));
std::cout << std::endl;

scalar_t num = 0.7f;
auto lS = logit(num);
auto sS = sigmoid(alpha * lS + beta);

std::cout << sS << std::endl;

return 0;
}


EDIT



This spawned a pretty interesting discussion about how to use constants in these type agnostic templates. Surprisingly there seems to be an answer. Examining the
sigmoid
function, we see that it also uses
float
constants with a
valarray<double>
but doesnt exhibit a compiler error. This is because, the
std::exp(-in)
line converts the
valarray<double>
to an expression template used the the standard library to optimize the computation, and for whatever reason it doesn't care about
float
or
double
(e.g. they provide the overload). So the solution I came up with was to add a unary + operator to the
logit
function which does absolutely nothing except convert the
valarray<double>
to an expression template which can work with the
float
constant.

Here is the update code sample

and the new
logit
function looks like this

template<typename T>
T logit(const T &in)
{
return std::log(in / (1.f - (+in)));
}


Note the unary + operator
(+in)

Answer

The operator - you are using is defined as

template <class T> std::valarray<T> operator- (const T& val, const std::valarray<T>& rhs);

This meas that it expects val to be the same type as the elements in the valarray. Since you are using a float when template argument deduction happens it sees that val is a float but rhs has a element type of double. since these types do not match the deduction fails and you get a compiler error. Remember no conversions happen during template argument deduction.