Why does std::lerp not work with any type that has implemented required operations?

Viewed 1831

After learning about std::lerp I tried to use it with strong types, but it fails miserably since it only works for built in types...

#include <iostream>
#include <cmath>

struct MyFloat{

  float val = 4.7;
  MyFloat operator *(MyFloat other){
      return MyFloat(other.val*val);
  }
  MyFloat operator +(MyFloat other){
      return MyFloat(other.val+val);
  }
  MyFloat operator -(MyFloat other){
      return MyFloat(other.val-val);
  }
};
int main()
{
    MyFloat a{1}, b{10};
    //std::lerp(a, b, MyFloat{0.3}); :(
    std::lerp(a.val, b.val, 0.3f);
}

My question is: Is there a good reason why C++20 introduced a function/algorithm that is not generic?

2 Answers

It would be impossible for std::lerp to provide its guarantees about numerical behavior for arbitrary types that happen to provide some arithmetic operators. (There’s no way for the library to detect that your example merely forwards them to the builtin float versions.)

While requirements could be imposed on the parameter type to allow a correct implementation, they would need to be exceedingly detailed for MyFloat to be handled with the same performance and results as float. For example, the implementation may need to compare values of the parameter type (which your type doesn’t support!) and can capitalize on the spacing between floating-point values to provide monotonicity guarantees near t=1.

Since those guarantees are the entire point of the function (the naïve formulas are trivial), it’s not provided at all in a generic form.

Is there a good reason why C++20 introduced a function/algorithm that is not generic?

Implementing it for a small set of types makes it easier to ensure correct results when mixing different argument types and dealing with possible implicit conversions (not to mention ambiguous overloads). As the last overload on cppreference (which is likely a template) specifies, the types are adjusted such that as little precision is lost as possible.

How can the same be achieved when the list of types is open ended? And a client programmer injects whatever meaning they want into overloaded operators? I'd say its pretty much impossible.

And it's nothing new, take std::pow for instance, which had a similar overload added to it in C++11. The standard library utilities that deal with numerical data are always specified only for types the implementation is aware of.

If lerp makes sense for your custom type, then you can add overloads into your custom namespace. ADL will find it, and generic code that is build on top of

using std::lerp;
lerp(arg1, arg2, arg3);

can be made to work for your custom type too.

Related