Wezwanie do lambda jest dwuznaczne, pomimo wyraźnego podania typu zwrotu

11

Przeciążona funkcja powinna przyjmować oba funktory, biorąc pod uwagę, że typ lambda jest rozstrzygalny (można ją wyrzucić std::function(proszę poprawić mnie, jeśli się mylę). Pytanie brzmi: Dlaczego poniżej jest błąd kompilacji, mimo że jawnie jest to typ lambda zdefiniowany? ( [&]() -> Type {})

Pamiętaj, że dla mojego obecnego rozwiązania potrzebuję przechwytywania przez referencję, dlatego kod zawiera logikę.

Poniższy przykład opisuje problem:

#include <iostream>
#include <string>    
#include <functional>

void do_some(std::function<void(int)> thing) 
{
   thing(5);
}

void do_some(std::function<bool(int)> thing)
{
   if (thing(10)) 
   {
      std::cout << "it's true!" << std::endl;
   }
}

int main()
{
   int local_to_be_modified = 0;
   do_some(
      [&](int in)
      {
         local_to_be_modified = in;
         std::cout << "This is void-" << std::endl;
      }
   );
   do_some(
      [&](int in) -> bool
      { 
         // error: call to 'do_some' is ambiguous
         local_to_be_modified += in;
         std::cout << "This is bool-" << std::endl;
         return true;
      }
   );
}
David Tóth
źródło
6
Ponieważ std::function<void(int)>można go zbudować nawet z lambda, które coś zwraca (co powoduje, że zwracana wartość jest ignorowana).
HolyBlackCat
1
Nawiasem mówiąc, wyraźne określenie typu zwracanego tej lambdy nic nie robi.
Deduplicator

Odpowiedzi:

8

Ponieważ 2. zwracane wyrażenie lambda boolmoże zostać przekonwertowane na oba std::function<void(int)>i std::function<bool(int)>niejawnie.

std::function ma konstruktor konwertujący:

template< class F >
function( F f );

Ten konstruktor nie bierze udziału w rozdzielczości przeciążenia chyba f jest wywoływalnym dla typów argumentów args ... i typ zwracany R. (od C ++ 14)

Jako definicja Callable ,

Następujące wyrażenia muszą być poprawne:

INVOKE<R>(f, std::declval<ArgTypes>()...)

gdzie INVOKE (f, t1, t2, ..., tN) jest zdefiniowane tak, static_cast<void>(INVOKE(f, t1, t2, ..., tN))jakby R ma prawdopodobnie kwalifikację cv void, w przeciwnym razie INVOKE (f, t1, t2, ..., tN) , domyślnie konwertowane na R

Zauważ, że drugi zwracany lambda bool, dla std::function<void(int)>, jak pokazano powyżej, static_cast<void>(INVOKE(f, t1, t2, ..., tN))jest prawidłowym wyrażeniem (zwracany booljest właśnie konwertowany na void). Następnie może również przekształcić się w std::function<void(int)>sposób domyślny i powodować problem niejednoznaczności.

songyuanyao
źródło
6

Możesz jawnie static_castlambda do właściwego typu

using FunBoolRet = std::function<bool(int)>;

do_some(static_cast<FunBoolRet >([&](int in) 
   {
      local_to_be_modified += in;
      std::cout << "This is bool-" << std::endl;
      return true;
   }));

Lub zapisz lambda do odpowiedniego std::function<bool(int)>typu i przekaż do funkcji (jeśli do_some(lmda)powinna być wywoływana wiele razy)

FunBoolRet lmda = [&](int in)
{
    local_to_be_modified += in;
    std::cout << "This is bool-" << std::endl;
    return true;
};    
do_some(lmda); // pass the lambda

Lub, jak sugerował @MaxLanghof , po prostu budujstd::function<bool(int)> z lambda w drodze

do_some(FunBoolRet{
   [&](int in) 
   {
      local_to_be_modified += in;
      std::cout << "This is bool-" << std::endl;
      return true;
   }
});
JeJo
źródło
Możesz pominąć static_casti po prostu zbudować std::functionbezpośrednio z niego. To wszystko dzieje się podczas niejawnej konwersji.
Max Langhof,
Chodzi mi o to, że dosłownie możesz po prostu usunąć static_cast<i trwać >i zrobi to samo, ale przy mniejszym pisaniu. Nie potrzebuje więcej linii ani nic. godbolt.org/z/fQTqF4
Max Langhof