Neden std :: function aşırı yük çözünürlüğüne katılmıyor?


17

Aşağıdaki kod derlemek biliyorum.

void baz(int i) { }
void baz() {  }


class Bar
{
    std::function<void()> bazFn;
public:
    Bar(std::function<void()> fun = baz) : bazFn(fun){}

};

int main(int argc, char **argv)
{
    Bar b;
    return 0;
}

Çünkü bu yazıdastd::function okuduğum gibi aşırı yük çözünürlüğünü dikkate almamaları söyleniyor .

Bu tür bir çözümü zorlayan teknik sınırlamaları tam olarak anlamıyorum.

Ben okumak çeviri aşamaları ve şablonları cppreference üzerinde, ama ben dair karşı bulamadığı herhangi muhakeme düşünemiyorum. Yarım bir katmana (hala C ++ için yeni) açıklanınca, çeviri aşaması neyi ve hangi aşamada derlemeyi başaramaz?


1
@Evg ancak OP örneğinde kabul edilmesi mantıklı olan yalnızca bir aşırı yükleme var.
Örneğinizde

Yanıtlar:


13

Bunun "çeviri aşamaları" ile bir ilgisi yoktur. Sadece yapıcıları ile ilgili std::function.

Bkz, std::function<R(Args)>verilen işlevin tam olarak tür olmasını gerektirmez R(Args). Özellikle, bir işlev işaretçisi verilmesini gerektirmez. Herhangi çağrılabilir tipini (üye işlev işaretçisi, bir aşırı vardır bazı nesne alabilir operator()kadar uzun Invokable olduğu gibi) sanki o aldı Argsparametreleri ve döner bir şey dönüştürülebilir R (eğer yoksa Rolduğunu void, hiçbir şey dönebilirsiniz).

Bunu yapmak için, uygun yapıcı std::functionşırası bir olmak şablon : template<typename F> function(F f);. Yani, herhangi bir işlev türünü alabilir (yukarıdaki kısıtlamalara tabi).

İfade baz, bir aşırı yük kümesini temsil eder. Bu ifadeyi aşırı yük setini çağırmak için kullanırsanız, sorun değil. Bu ifadeyi belirli bir işlev işaretçisi alan bir işleve parametre olarak kullanırsanız, C ++ ayarlanan aşırı yükü tek bir çağrıya indirgeyebilir ve böylece iyi hale getirebilir.

Bununla birlikte, bir işlev bir şablon olduğunda ve bu parametrenin ne olduğunu anlamak için şablon bağımsız değişkenini kesinti kullandığınızda, C ++ artık aşırı yük kümesinde doğru aşırı yükün ne olduğunu belirleme yeteneğine sahip değildir. Bu yüzden doğrudan belirtmelisiniz.


Affedersem yanlış bir şey varsa, ama neden C ++ mevcut aşırı yükleri ayırt etmek için hiçbir yeteneği yok? Şimdi görüyorum ki std :: function <T> herhangi bir uyumlu türü kabul ediyor, sadece tam bir eşleşme değil, aynı zamanda bu iki baz () aşırı yüklemeden sadece biri belirtilen parametreleri almış gibi çağrılıyor . Neden belirsiz olmak imkansız?
TuRtoise

Çünkü C ++ 'ın gördüğü tüm alıntılarım. Neyle eşleşmesi gerektiğini bilmiyor. Esasen, doğru cevabın açıkça C ++ kodundan (kod bu şablon beyanıdır) ne olduğu açık olmayan bir şeye karşı bir aşırı yük seti kullandığınızda , dil sizi ne demek istediğinizi açıklamaya zorlar.
Nicol Bolas

1
@TuRtoise: functionSınıf şablonundaki template parametresi önemsizdir . Önemli olan aradığınız yapıcıdaki template parametresidir . Hangi sadece typename F: aka, herhangi bir tür.
Nicol Bolas

1
@TuRtoise: Sanırım bir şeyi yanlış anlıyorsunuz. "Sadece F" çünkü şablonlar böyle çalışır. İmzadan itibaren, bu işlev oluşturucu herhangi bir türü alır, bu nedenle bunu şablon bağımsız değişkeni kesinti ile çağırmak için yapılan herhangi bir girişim türü parametreden çıkarır. Aşırı yük kümesine kesinti uygulama girişimleri derleme hatasıdır. Derleyici, hangisinin işe yaradığını görmek için olası her türü kümeden çıkarmaya çalışmaz.
Nicol Bolas

1
"Bir aşırı yük kümesine kesinti uygulama girişimi derleme hatasıdır. Derleyici, hangilerinin işe yaradığını görmek için olası her türü kümeden çıkarmaya çalışmaz." Tam olarak ne eksikti, teşekkür ederim :)
TuRtoise

7

Aşırı yük çözünürlüğü yalnızca (a) bir işlevin / operatörün adını çağırdığınızda veya (b) onu açık bir imzayla bir işaretçiye (işlev veya üye işlevine) dönüştürdüğünde gerçekleşir.

Burada da hiçbiri olmuyor.

std::functionimzasıyla uyumlu herhangi bir nesneyi alır . Özellikle bir işlev işaretçisi almaz. (lambda std işlevi değildir ve std işlevi lambda değildir)

Şimdi homebrew fonksiyon varyantlarımda, imza için de tam da bu nedenle R(Args...)bir R(*)(Args...)argümanı (tam eşleşme) kabul ediyorum . Ancak "tam eşleşme" imzalarını "uyumlu" imzaların üzerine çıkardığı anlamına gelir.

Temel sorun, bir aşırı yük kümesinin bir C ++ nesnesi olmamasıdır. Bir aşırı yük setini adlandırabilirsiniz, ancak "doğal" olarak geçemezsiniz.

Şimdi, böyle bir fonksiyonun sahte aşırı yük setini oluşturabilirsiniz:

#define RETURNS(...) \
  noexcept(noexcept(__VA_ARGS__)) \
  -> decltype(__VA_ARGS__) \
  { return __VA_ARGS__; }

#define OVERLOADS_OF(...) \
  [](auto&&...args) \
  RETURNS( __VA_ARGS__(decltype(args)(args)...) )

bu, işlev adında aşırı yük çözünürlüğü yapabilen tek bir C ++ nesnesi oluşturur.

Makroları genişleterek şunu elde ederiz:

[](auto&&...args)
noexcept(noexcept( baz(decltype(args)(args)...) ) )
-> decltype( baz(decltype(args)(args)...) )
{ return baz(decltype(args)(args)...); }

bu da yazmak için can sıkıcı bir durum. Daha basit, sadece biraz daha az kullanışlı bir sürüm burada:

[](auto&&...args)->decltype(auto)
{ return baz(decltype(args)(args)...); }

herhangi bir sayıda argüman alan bir lambdamız var, sonra onları mükemmel bir şekilde iletiyoruz baz.

Sonra:

class Bar {
  std::function<void()> bazFn;
public:
  Bar(std::function<void()> fun = OVERLOADS_OF(baz)) : bazFn(fun){}
};

İşler. Aşırı yük çözünürlüğünü fun, funbir aşırı yük setini doğrudan ( yerine çözemediği) geçmek yerine depoladığımız lambda'ya erteliyoruz .

C ++ dilinde bir işlev adını bir aşırı yük ayar nesnesine dönüştüren bir işlem tanımlamak için en az bir öneri vardır. Böyle bir standart teklif standartta olana kadar OVERLOADS_OFmakro yararlıdır.

Bir adım daha ileri gidebilir ve cast-uyumlu-fonksiyon-pointer'ı destekleyebilirsiniz.

struct baz_overloads {
  template<class...Ts>
  auto operator()(Ts&&...ts)const
  RETURNS( baz(std::forward<Ts>(ts)...) );

  template<class R, class...Args>
  using fptr = R(*)(Args...);
  //TODO: SFINAE-friendly support
  template<class R, class...Ts>
  operator fptr<R,Ts...>() const {
    return [](Ts...ts)->R { return baz(std::forward<Ts>(ts)...); };
  }
};

ama bu genişlemeye başlıyor.

Canlı örnek .

#define OVERLOADS_T(...) \
  struct { \
    template<class...Ts> \
    auto operator()(Ts&&...ts)const \
    RETURNS( __VA_ARGS__(std::forward<Ts>(ts)...) ); \
\
    template<class R, class...Args> \
    using fptr = R(*)(Args...); \
\
    template<class R, class...Ts> \
    operator fptr<R,Ts...>() const { \
      return [](Ts...ts)->R { return __VA_ARGS__(std::forward<Ts>(ts)...); }; \
    } \
  }

5

Burada sorun derleyiciye işaretçi çürümesi için işlevi nasıl yapacağını söyleyen bir şey yoktur. Eğer varsa

void baz(int i) { }
void baz() {  }

class Bar
{
    void (*bazFn)();
public:
    Bar(void(*fun)() = baz) : bazFn(fun){}

};

int main(int argc, char **argv)
{
    Bar b;
    return 0;
}

Daha sonra kod işe yarayacaktır, çünkü derleyici hangi atamayı yaptığınız bir beton türü olduğu için hangi işlevi istediğinizi bilir.

Kullandığınızda std::function, formun bulunduğu fonksiyon nesnesi yapıcısı

template< class F >
function( F f );

ve bu bir şablon olduğundan, iletilen nesnenin türünü çıkarması gerekir. çünkü bazbir aşırı işlev orada şablon kesinti başarısız böylece sonucuna varılabilir tek türüdür ve bir hata olsun. Kullanmak zorunda kalacaksın

Bar(std::function<void()> fun = (void(*)())baz) : bazFn(fun){}

tek bir tür zorlamak ve kesinti yapılmasını sağlamak.


"baz aşırı yüklenmiş bir fonksiyon olduğundan çıkartılabilecek tek bir tür yoktur" ancak C ++ 14 "ten beri bu yapıcı f argüman türleri Args ... ve return R tipi için çağrılmadıkça aşırı yük çözünürlüğüne katılmaz." emin değilim, ama bunun belirsizliği çözmek için yeterli olacağını
beklemeye yakındım

3
@ formerlyknownas_463035818 Ancak bunu belirlemek için önce bir tür çıkarması gerekir ve aşırı yüklenmiş bir ad olduğu için bunu yapamaz.
NathanOliver

1

Derleyici, yapıcıya hangi aşırı yükü geçeceğine karar verir, std::functiontek bildiği şey std::function, kurucunun herhangi bir tür almak için ayarlanmasıdır. Her iki aşırı yükü de denemek ve birincisinin derlemediğini, ikincisinin de derleme yeteneğine sahip değildir.

Bunu çözmenin yolu derleyiciye hangi aşırı yükü istediğinizi açıkça belirtmektir static_cast:

Bar(std::function<void()> fun = static_cast<void(*)()>(baz)) : bazFn(fun){}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.