5

Testcase

Let the return type of a function auto foo(T f) be the same as when calling sin(f) from header cmath in cases where f is an intrinsic datatype:

template <typename T>
auto foo(T f) -> decltype(sin(f))
{
    using std::sin;
    return sin(f);
}

This is broken. The sin(f) within decltype is not looked up within std, hence only the C variant sin(double) is found, whose return type is double. The following program demonstrates that:

#include <cmath>
#include <iostream>
#include <typeinfo>

namespace meh {
    struct Nanometer {};
    struct SinfulNanometer {};
    SinfulNanometer sin(Nanometer) { return SinfulNanometer(); }
}

template <typename T>
auto foo(T f) -> decltype(sin(f))
{
    using std::sin;
    std::cout << typeid(decltype(sin(f))).name() << '\n';
}


int main () {
    std::cout << typeid(decltype(foo(0.))).name() << '\n'
              << typeid(decltype(foo(0.f))).name() << '\n'
              << typeid(decltype(foo(meh::Nanometer()))).name() << '\n';
              
    foo(0.);
    foo(0.f);
    foo(meh::Nanometer());
}

Output:

d
d
N3meh15SinfulNanometerE
d
f
N3meh15SinfulNanometerE

The output suggests that the return type is always double for foo->float and foo->double, only within foo(), the correct sin(float|double) is found because of the using std::sin, which imports all overloads.

I wonder if such case was taken into consideration in the gremium already, but that's not my question.

Question

My question is:

What is a sensible way to let foo have the same return-type as a function whose name is either in namespace std or ADL-looked-up?

Non working workaround:

template <typename T>
auto foo(T f) -> decltype(std::sin(f)); // Will miss sin(Nanometer)

Standard proposal?

template <typename T>
auto foo(T f) -> decltype(using std::sin; sin(f));

Using permutations of enable_if is in the way of code being self-documenting. enable_if is a nice exercise of metaprogramming. But to get a quick grasp of a seemingly simple function, imho not the right thing, therefore not in the spirit of maintainability.


edit: I am also using decltype to use SFINAE less obscurely, so C++14 with it's new auto foo() {....} declarations might not be of help.

Community
  • 1
  • 1
Sebastian Mach
  • 38,570
  • 8
  • 95
  • 130

2 Answers2

10

You can use a detail namespace and some wrapping:

namespace detail {
    using std::sin;

    template<typename T>
    auto foo(T f) -> decltype(sin(f)) { return sin(f); }
}

template<typename T>
auto foo(T f) -> decltype(detail::foo(f)) { return detail::foo(f); }
  • 3
    Oh yeah, that's nice. Everything can be solved with one more indirection :P – Sebastian Mach Nov 02 '13 at 16:58
  • 4
    +1 Basically a good solution, except for the name `detail` as this will probably be used quite often in larger project and you just injected `using std::sin;` - this is the reason I used a more specific namespace name for this case to contain the `using`. – Daniel Frey Nov 02 '13 at 16:59
  • @DanielFrey: I agree on that, I just see `detail` as a placeholder here. I am going to name it appropriately, something like `intrinsic_or_adl` or `mixin_cmath_names`, something. Actually, I am talking about the whole `cmath` header that I overloaded for an RGB-triplet, and which I now want to generalize to work with any array-indexable type. – Sebastian Mach Nov 02 '13 at 17:00
8

You need one level of indirection:

namespace find_sin
{
    using std::sin;
    template<typename T>
    using type = decltype(sin(std::declval<T>()));
}

template <typename T>
find_sin::type<T> foo(T f)
{
    // ...
}
Daniel Frey
  • 55,810
  • 13
  • 122
  • 180
  • Oh yeah, that's nice. Everything can be solved with one more indirection :P Unfortunately, rightfold was some 0.7 secs faster. – Sebastian Mach Nov 02 '13 at 16:59