Search code examples
c++c++11decltypeargument-dependent-lookup

`decltype` and mixing ADL-lookup with non-ADL-lookup


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.


Solution

  • 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); }