Skip the std :: function templated template functor

Here's a snippet of code:

#include <functional>
#include <iostream>
#include <memory>

template <typename T>
using Callback = std::function<void(const T)>;

template <typename T>
void Call(const T yyy, const Callback<T>& callback) {
    callback(yyy);
}

template <typename T>
class MyCallback {
public:
    explicit MyCallback(const T counter_init) : counter_{counter_init} {}
    void operator ()(const T xxx) {
        std::cout << counter_ << '\t' << xxx << std::endl;
        ++counter_;
    }
private:
    T counter_;
};

int main() {
    const auto callback = std::make_unique<MyCallback<int>>(0);
    Call(111, *callback);  // expected output is "0 \t 111"
    Call(222, *callback);  // expected output is "1 \t 222"
    return 0;
}

      

Clang says it cannot match std :: function against MyCallback, while g ++ thinks MyCallback is derived from Callback.

clang ++ -std = C ++ 14 main.cpp && & && &. / a.out

g ++ -std = C ++ 14 main.cpp && & && &. / a.out

The easiest way to find out what to fix this problem is to use a template instead of Callback so that Call is defined like this:

template <typename T, typename F>
void Call(const T yyy, F&& callback) {
    callback(yyy);
}

      

But it would be unclear to the next developer what kind of signature the callback has.

Can someone clarify what is happening in the first example from the compiler's point of view and how can I fix this without applying the hack above?

+3


source to share


3 answers


Change Call

to:

template <class T, class F,
  class R=typename std::result_of<F&(const T&)>::type
>
void Call(const T& yyy, F&& f) {
  f(yyy);
}

      

we now call f

on yyy

, and (assuming you have a C ++ 11 compiler that implements safe SFINAE result_of

) the overload only works if you can call f

on yyy

.

std::function

is not generally accepted. This is a way of entering erasure types before "signature call", copying the called, and destroying the called.

Type-erase and type subtraction are opposite operations. Doing both in one step is usually a sign of a design flaw. std::function

should very rarely be inferred from the signature of the past call.

Instead, determine how you are going to use the feature. Then erase the erase before using the signature, or just check that you are using the signature and don't erase the font at all.

If you have multiple possible signatures to use, test them against each one, send the tag to the correct style erase path, and enter erase there.



This suggestion result_of

is optional, but it greatly improves error reporting. It also makes the error a detectable "early" unsuccessful overload rather than a hard error. The clause result_of

can be converted to static_assert

in a body, which would generate an even clearer error message, but would not be "late" after the overload was resolved.

An alternative approach, blocking the deduction on function

, is:

template<class T>struct tag{using type=T;};
template<class Tag>using type_t=typename Tag::type;
template<class T>using block_deduction=type_t<tag<T>>;

template <typename T>
using Callback = block_deduction<std::function<void(const T)>>;

      

and now

template <class T>
void Call(const T& yyy, const Callback<T>& callback) {
  callback(yyy);
}

      

works. It still erases the type callback

unnecessarily (with an overhead).

The deduction is blocked by a circular trip through struct

and gets ::type

. According to the standard, such dependent types are never inferred.

+4


source


std::function<void(const T)>

can be plotted by value with *callback

. A task Call

is a function template. The compiler cannot automatically instantiate void Call(const int yyy, const Callback<int>& callback)

from Call(111, *callback);

because it does not match the call. The transformation is required to invoke an instance with Call(111, *callback);

. But the automatic deduction of the template argument ignores such conversions. He only considers the exact match. You could manually create an instance. So one way to fix your code will change

Call(111, *callback);  // expected output is "0 \t 111"
Call(222, *callback);  // expected output is "1 \t 222"

      

to



Call<int>(111, *callback);  // expected output is "0 \t 111"
Call<int>(222, *callback);  // expected output is "1 \t 222"

      

Thus, temporary std::function<void(const T)>

if the first value is constructed with *callback

. The function parameter const Callback<int>& callback

in is Call

then tied to this temporary. Again, this is about conversion. The function signature is not an exact match for the call.

+4


source


From [temp.arg.explicit]:

Implicit conversions (section 4) will be performed on the function argument to convert it to the type of the corresponding function parameter , if the type of the parameter does not contain template parameters that are involved in the derivation of the template argument. [Note. Template parameters do not appear in the template argument if explicitly specified. For example,

template<class T> void f(T);

class Complex {
    Complex(double);
};

void g() {
    f<Complex>(1); // OK, means f<Complex>(Complex(1))
}

      

-end note]

In your case, it const Callback<T>&

contains a template parameter that participates in the inference of the template argument, so implicit conversion (i.e. MyCallback<int>

to std::function<void(const int)>

) is allowed .

To use std::function

, you need the argument callback

not to participate in the subtraction of template arguments. I.e:

Call<int>(111, *callback); // no argument deduction done here

      

Alternatively you can just output callback

yourself, no need to erase the styles:

template <typename T, typename F>
void Call(const T yyy, F&& callback) {
    callback(yyy);
}

      

Finally, if you really want to erase a type, you can manually create yours callback

inside Call

:

template <typename T, typename F>
void Call(const T yyy, F&& f) {
    Callback<T> callback(std::forward<F>(f));
    callback(yyy);
}

      

+2


source







All Articles