Aşırı yüklenmiş bir işleve nasıl bir işaretçi belirtebilirim?


137

Aşırı yüklenmiş bir fonksiyonu std::for_each()algoritmaya aktarmak istiyorum . Örneğin,

class A {
    void f(char c);
    void f(int i);

    void scan(const std::string& s) {
        std::for_each(s.begin(), s.end(), f);
    }
};

Derleyicinin f()yineleyici türüne göre çözümlenmesini beklerdim . Görünüşe göre, (GCC 4.1.2) bunu yapmaz. Peki hangisini f()istediğimi nasıl belirleyebilirim ?


Yanıtlar:


137

İşlev işaretçisi türünün ima ettiği işlev imzasına göre static_cast<>()hangisinin kullanılacağını belirtmek fiçin kullanabilirsiniz:

// Uses the void f(char c); overload
std::for_each(s.begin(), s.end(), static_cast<void (*)(char)>(&f));
// Uses the void f(int i); overload
std::for_each(s.begin(), s.end(), static_cast<void (*)(int)>(&f)); 

Veya bunu da yapabilirsiniz:

// The compiler will figure out which f to use according to
// the function pointer declaration.
void (*fpc)(char) = &f;
std::for_each(s.begin(), s.end(), fpc); // Uses the void f(char c); overload
void (*fpi)(int) = &f;
std::for_each(s.begin(), s.end(), fpi); // Uses the void f(int i); overload

Eğer füye fonksiyonudur, o zaman kullanıma gerek mem_fun, ya da durum için kullanmak bu Doktor Dobb'un makalede sunulan çözüm .


1
Teşekkürler! Yine de, muhtemelen f()bir sınıfın üyesi olması nedeniyle bir sorunum var (yukarıdaki düzenlenmiş örneğe bakın)
davka

9
@the_drow: İkinci yöntem aslında çok daha güvenlidir, aşırı yüklenmelerden biri ortadan kalkarsa ilk yöntem sessizce tanımsız davranış verirken, ikinci yöntem derleme zamanında sorunu yakalar.
Ben Voigt

3
@ BenVoigt Hmm, bunu vs2010'da test ettim ve static_cast'in derleme zamanında sorunu yakalayamayacağı bir durum bulamadım. "Kapsamda bu ada sahip işlevlerin hiçbiri hedef türüyle eşleşmiyor" içeren bir C2440 verdi. Açıklayabilir misin?
Nathan Monteleone

5
@Nathan: Düşündüğüm olası reinterpret_cast. Çoğu zaman bunun için kullanılan C tarzı dökümler görüyorum. Benim kuralım sadece işlev işaretçileri üzerindeki dökümlerin tehlikeli ve gereksiz olmasıdır (ikinci kod snippet'inin gösterdiği gibi, örtük bir dönüşüm vardır).
Ben Voigt

3
Üye işlevleri için:std::for_each(s.begin(), s.end(), static_cast<void (A::*)(char)>(&A::f));
sam-w

29

Lambdas kurtarmaya! (not: C ++ 11 gerekir)

std::for_each(s.begin(), s.end(), [&](char a){ return f(a); });

Veya lambda parametresi için decltype kullanarak:

std::for_each(s.begin(), s.end(), [&](decltype(*s.begin()) a){ return f(a); });

Polimorfik lambdalarla (C ++ 14):

std::for_each(s.begin(), s.end(), [&](auto a){ return f(a); });

Veya aşırı yüklemeyi kaldırarak belirsizleştirme (yalnızca ücretsiz işlevler için çalışır):

void f_c(char i)
{
    return f(i);
}

void scan(const std::string& s)
{
    std::for_each(s.begin(), s.end(), f_c);
}

Lambdas için Yaşasın! Gerçekten de, aşırı yük çözünürlüğü sorununa mükemmel bir çözüm. (Bunu da düşündüm, ancak suları çamurlamamak için cevabımın dışında bırakmaya karar verdim.)
aldo

Aynı sonuç için daha fazla kod. Bence lambdalar bunun için yapılmadı.
Tomáš Zato - Monica'yı

@ TomášZato Fark, bu cevabın işe yaraması ve kabul edilen yanıtın (OP tarafından gönderilen örnek için - ayrıca kullanmanız mem_fnve bindBTW'nin de C ++ 11 olması). Ayrıca, gerçekten bilgiçlik istiyorsak [&](char a){ return f(a); }28 karakter ve static_cast<void (A::*)(char)>(&f)35 karakterdir.
milleniumbug

1
@ TomášZato Buyurun coliru.stacked-crooked.com/a/1faad53c4de6c233 bu daha net hale getirmek için emin nasıl
milleniumbug

18

Neden çalışmıyor

Derleyicinin f()yineleyici türüne göre çözümlenmesini beklerdim . Görünüşe göre, (gcc 4.1.2) bunu yapmaz.

Durum böyle olsaydı harika olurdu! Ancak, for_eachaşağıdaki gibi bildirilen bir işlev şablonudur:

template <class InputIterator, class UnaryFunction>
UnaryFunction for_each(InputIterator, InputIterator, UnaryFunction );

Şablon çıkarımının UnaryFunction, çağrı noktasında bir tür seçmesi gerekir . Ancak fbelirli bir türü yoktur - aşırı yüklenmiş bir işlevdir, fher biri farklı türlerde birçok s vardır. İstediğini for_eachbelirterek şablon kesinti sürecine yardımcı olmanın güncel bir yolu yoktur f, dolayısıyla şablon kesinti başarısız olur. Şablon çıkarımının başarılı olması için çağrı sitesinde daha fazla iş yapmanız gerekir.

Düzeltmek için genel çözüm

Birkaç yıl ve C ++ 14 daha sonra burada umut. Aksine kullanım daha static_cast(şablon kesinti hangi "tespit" tarafından başarılı olmak için izin verecek olan fbiz kullanıma istiyoruz ama elle "düzeltme" Doğru birine aşırı yük çözünürlüğü yapmanızı gerektirir), bizim için derleyici çalışması yapmak istiyorum. fBazı argümanları çağırmak istiyoruz . Mümkün olan en genel şekilde, bu:

[&](auto&&... args) -> decltype(auto) { return f(std::forward<decltype(args)>(args)...); }

Yazması gereken çok şey var, ancak bu tür bir sorun sık sık rahatsız edici bir şekilde ortaya çıkıyor, bu yüzden bunu bir makroya (iç çekerek) sarabiliriz:

#define AS_LAMBDA(func) [&](auto&&... args) -> decltype(func(std::forward<decltype(args)>(args)...)) { return func(std::forward<decltype(args)>(args)...); }

ve sonra sadece kullanın:

void scan(const std::string& s) {
    std::for_each(s.begin(), s.end(), AS_LAMBDA(f));
}

Bu, derleyicinin yapmasını istediğiniz şeyi tam olarak yapar - adın fkendisinde aşırı yük çözünürlüğü gerçekleştirir ve sadece doğru olanı yapar. Bu, fserbest bir işlev veya üye bir işlev olup olmadığına bakılmaksızın çalışır.


7

Sorunuzu cevaplamak için değil, bulabilen tek kişi ben miyim

for ( int i = 0; i < s.size(); i++ ) {
   f( s[i] );
}

for_eachbu durumda silico tarafından önerilen alternatif daha basit ve daha kısa ?


2
muhtemelen, ama sıkıcı :) Ayrıca, eğer [] operatörü önlemek için yineleyici kullanmak istiyorsanız, bu daha uzun olur ...
davka

3
@Davka Sıkıcı istediğimiz bu. Ayrıca, yineleyiciler, endişe duyuyorsanız, op [kullanmaktan daha hızlı (daha yavaş olabilir) değildir.

7
Algoritmalar döngüler için tercih edilmelidir, çünkü daha az hataya eğilimlidirler ve optimizasyon için daha iyi fırsatlara sahip olabilirler. Bununla ilgili bir yerde bir makale var ... işte burada: drdobbs.com/184401446
AshleysBrain

5
@Ashley "Daha az hata eğilimli" hakkında bazı objektif istatistikler görene kadar buna inanmaya gerek görmüyorum. Ve makaledeki Meyers, yineleyicileri kullanan döngülerden bahsediyor gibi görünüyor - yineleyicileri kullanmayan döngülerin verimliliğinden bahsediyorum - kendi kriterlerim, optimize edildiğinde marjinal olarak daha hızlı olduğunu iddia ediyor - kesinlikle daha yavaş değil.

1
İşte ben, çözümünüzü daha iyi buluyorum.
peterh - Monica'yı yeniden haziran

5

Buradaki sorun aşırı yük çözünürlüğü değil, aslında şablon parametresi kesintisi gibi görünüyor . @In silico'nun mükemmel cevabı genel olarak belirsiz bir aşırı yükleme problemini çözecek olsa da , std::for_each(veya benzeri) ile uğraşırken şablon parametrelerini açıkça belirtmek en iyi çözüm gibi görünüyor :

// Simplified to use free functions instead of class members.

#include <algorithm>
#include <iostream>
#include <string>

void f( char c )
{
  std::cout << c << std::endl;
}

void f( int i )
{
  std::cout << i << std::endl;
}

void scan( std::string const& s )
{
  // The problem:
  //   error C2914: 'std::for_each' : cannot deduce template argument as function argument is ambiguous
  // std::for_each( s.begin(), s.end(), f );

  // Excellent solution from @In silico (see other answer):
  //   Declare a pointer of the desired type; overload resolution occurs at time of assignment
  void (*fpc)(char) = f;
  std::for_each( s.begin(), s.end(), fpc );
  void (*fpi)(int)  = f;
  std::for_each( s.begin(), s.end(), fpi );

  // Explicit specification (first attempt):
  //   Specify template parameters to std::for_each
  std::for_each< std::string::const_iterator, void(*)(char) >( s.begin(), s.end(), f );
  std::for_each< std::string::const_iterator, void(*)(int)  >( s.begin(), s.end(), f );

  // Explicit specification (improved):
  //   Let the first template parameter be derived; specify only the function type
  std::for_each< decltype( s.begin() ), void(*)(char) >( s.begin(), s.end(), f );
  std::for_each< decltype( s.begin() ), void(*)(int)  >( s.begin(), s.end(), f );
}

void main()
{
  scan( "Test" );
}

4

C ++ 11'i kullanmanın bir sakıncası yoksa, statik döküme benzer (ancak daha az çirkin) akıllı bir yardımcı:

template<class... Args, class T, class R>
auto resolve(R (T::*m)(Args...)) -> decltype(m)
{ return m; }

template<class T, class R>
auto resolve(R (T::*m)(void)) -> decltype(m)
{ return m; }

(Üye işlevleri için çalışır; bağımsız işlevler için çalışmak üzere nasıl değiştirileceği açık olmalıdır ve her iki sürümü de sağlayabilmeniz gerekir ve derleyici sizin için doğru olanı seçecektir.)

Önerdiği için Miro Knejp'e teşekkür ederiz: ayrıca bkz . Https://groups.google.com/a/isocpp.org/d/msg/std-discussion/rLVGeGUXsK0/IGj9dKmSyx4J .


OP'nin sorunu, aşırı yüklenmiş bir adı bir işlev şablonuna geçirememek ve çözümünüz, aşırı yüklenmiş bir adı bir işlev şablonuna geçirmeyi içeriyor mu? Bu tam olarak aynı problem.
Barry

1
@Barry Aynı sorun değil. Bu durumda şablon bağımsız değişkeninin çıkarılması başarılı olur. Çalışır (birkaç küçük ayar ile).
Oktalist

@Oktalist Sağladığınız Riçin çıkarılmadı. Bu cevapta bundan da bahsedilmiyor.
Barry

1
@Barry vermiyorum R, sağlıyorum Args. Rve Tçıkarılır. Cevabın geliştirilebileceği doğrudur. ( TBenim örneğimde hayır yok , çünkü bu bir işaretçi-üye değil, çünkü bu işe yaramaz std::for_each.)
örneğimde
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.