1

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 doubles (1.0) or ints (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;
}
Max Ehrlich
  • 2,479
  • 1
  • 32
  • 44
  • Promotion is not the problem. `valarray`'s `operator-` deduces the same type for both the left hand side (`T const&`) and right hand side (`valarray const&`). It's conflicted because it gets both `float` and `double` for the same template argument. You should be able to fix it by doing `1.0`. – David G Nov 02 '16 at 18:26
  • "Why cant float be promoted to double here?" What specific point in your code are you referring to as "here"? What promotion are you talking about? – AnT stands with Russia Nov 02 '16 at 18:30
  • @0x499602D2 You're right but why shouldnt an implicit conversion happen here? I cant really fix it by doing 1.0 because I don't have prior knowledge of `scalar_t` (or assume I don't for this discussion) – Max Ehrlich Nov 02 '16 at 18:30
  • @AnT At the point of compiler failure, which I probably should have said in the post is at the return from `logit` (you can see that in the coliru sample) – Max Ehrlich Nov 02 '16 at 18:31
  • @Max Ehrlich: In deduction context it is too early to think about any "promotions". E.g. `std::max(1.0, 1.0f)` does not compile for the same reason, even though the types are convertible. It is not the conversion that fails, it is the deductin that fails. – AnT stands with Russia Nov 02 '16 at 18:32
  • @MaxEhrlich Deduction of the template argument must happen before any conversion occurs. `T` is getting different types (`float` and `double`) compilation halts there. You can probably fix it by doing `T(1.0)` if you don't know the type of `T`. – David G Nov 02 '16 at 18:32
  • @0x499602D2 Well that would work except that T could be a `scalar_t` or a `valarray` in which case `T(1.0)` wouldnt work – Max Ehrlich Nov 02 '16 at 18:34
  • @AnT Right, the mechanism makes sense to me, I am just wondering how I can use a constant in a template function like this. All the operators work out to allow this to work fine on a scalar type or a `valarray` it's just that if I have a constant in my expression, then what type can it be? – Max Ehrlich Nov 02 '16 at 18:35
  • @MaxEhrlich Instead of editing the question, it would have been better to put the solution in an answer (nothing wrong with answering your own question) – njuffa Nov 02 '16 at 19:00
  • @njuffa True it would have been cleaner but what I have doesn't really answer the asked question (why doesnt this compile as written) which NathOliver did well – Max Ehrlich Nov 02 '16 at 19:46

2 Answers2

4

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.

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
  • "no conversions happen during template argument deduction" I think is the problem. So the obvious next question is, how can I fix it assuming I don't know about `scalar_t`? Also why shouldnt implicit conversions be allowed in this scenario? – Max Ehrlich Nov 02 '16 at 18:27
  • @MaxEhrlich Simplest would be to convert the literals to `scalar_t`'s. If the function will only be used for standard types you could always you the `value_type` member and cast the literals to that type. – NathanOliver Nov 02 '16 at 18:30
  • That would solve it for this sample, but assume I have no prior knowledge of `scalar_t` and no knowledge of the type of `T` at all (as you can see I've used it in the same as a `scalar_t` as well as a `valarray` both of which should work fine except for the missing implicit conversion) – Max Ehrlich Nov 02 '16 at 18:33
  • @MaxEhrlich Really I would just add a overload for a `std::valarray`. Do the special case in there and then you have the main template for everything else. – NathanOliver Nov 02 '16 at 18:35
  • That's how I handled it in the production code, but for my own purposes I am wondering if we can't do better. It seems maybe not – Max Ehrlich Nov 02 '16 at 18:36
  • @MaxEhrlich I do not see one. If you find one or someone else gives you a way I would love to hear about it. – NathanOliver Nov 02 '16 at 18:38
  • Well you're not going to flippin believe this but I actually did it I think. I was looking at `sigmoid` and realized that it doesnt have the same issue because by the time it gets to the constants it is a template expression which works for whatever reason. The solution is to add a unary + before doing anything with the valarray http://coliru.stacked-crooked.com/a/4c8c11cc06cb706f – Max Ehrlich Nov 02 '16 at 18:41
  • @MaxEhrlich I believe that is because unary + states *The function can be implemented with the return type different from std::valarray. In this case[...]* So the type you have is no longer a `valarray` but something like it and that can be used with the `float`. – NathanOliver Nov 02 '16 at 19:31
  • Exactly. It's using the expression templates which seems to provide a valid overload for the double or float (and I suspect int) case – Max Ehrlich Nov 02 '16 at 19:46
  • @MaxEhrlich I would remove the solution you found from your question and put it up as an answer since it shows how you could overcome the problem. – NathanOliver Nov 02 '16 at 19:48
1

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)

Also note that NathanOliver's accepted solution answers the question as asked

Max Ehrlich
  • 2,479
  • 1
  • 32
  • 44