Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Call operator with auto return type being chosen instead of constructor when using std::function

The following snippet:

#include <functional>

struct X {
    X(std::function<double(double)> fn); // (1)
    X(double, double);                   // (2)

    template <class T>
    auto operator()(T const& t) const {  // (3)
        return t.foo();
    }
};

int main() {
    double a, b;
    auto x = X(a, b);
    return 0;
}

...fails to compile with both clang (4.0.1) and g++ (6.3, 7.2) when using -std=c++14 — tested on OSX and godbolt.org.

But it compiles without problem if:

  • I remove constructor (1);
  • or I set the return type to a concrete type (e.g. double) in (3);
  • or if I use a trailing return type (-> decltype(t.foo())) in (3);
  • compile using -std=c++1z (thanks @bolov).

Maybe there is something obvious that I am missing here... Is there any issue with this code? Is this a bug?

like image 896
Holt Avatar asked Sep 19 '17 08:09

Holt


1 Answers

You are copy initializing x. And X is a tricky type for the following reasons:

  1. It can be constructed from any callable object that can be called with a double argument. And whose return type is convertible to a double.

  2. It is itself a callable object that can be called with a double argument. But the return type needs deducing.

  3. There's a compiler generated copy constructor for X.

The ambiguity is starting to become obvious here, I hope. There is need for overload resolution.

When your remove the first c'tor, it gets rid of the ambiguity in an obvious fashion. The interesting case is the function call operator.

You see, std::function can only be constructed from the callable it is passed (the templated c'tor will only participate in overload resolution) if the conversions between arguments to parameters, and between return types are possible. This is all done in an unevaluated context, so the templated function call operator isn't odr-used, and therefore not instantiated.

When the return type is a placeholder type, the std::function c'tor cannot easily resolve if it should be constructed or not. Compilation fails during overload resolution, even though had it succeeded, the copy c'tor of X would have been selected.


As @VTT suggested in a comment, marking the c'tor that accepts a std::function as explicit will also resolve the ambiguity. All on account of the compiler not having to rank the implicit conversion sequence at all.

like image 95
StoryTeller - Unslander Monica Avatar answered Nov 15 '22 01:11

StoryTeller - Unslander Monica