C ++ 'da SFINAE işlevine yaklaşımlar


40

SFINAE işlevini yoğun olarak bir projede kullanıyorum ve aşağıdaki iki yaklaşım (stil dışında) arasında herhangi bir fark olup olmadığından emin değilim:

#include <cstdlib>
#include <type_traits>
#include <iostream>

template <class T, class = std::enable_if_t<std::is_same_v<T, int>>>
void foo()
{
    std::cout << "method 1" << std::endl;
}

template <class T, std::enable_if_t<std::is_same_v<T, double>>* = 0>
void foo()
{
    std::cout << "method 2" << std::endl;
}

int main()
{
    foo<int>();
    foo<double>();

    std::cout << "Done...";
    std::getchar();

    return EXIT_SUCCESS;
}

Program çıktısı beklendiği gibi:

method 1
method 2
Done...

Stackoverflow'da yöntem 2'nin daha sık kullanıldığını gördüm, ancak yöntem 1'i tercih ediyorum.

Bu iki yaklaşımın farklı olduğu durumlar var mı?


Bu programı nasıl çalıştırıyorsunuz? Benim için derlenmeyecek.
alter igel

@alter igel bir C ++ 17 derleyicisine ihtiyaç duyacaktır. Bu örneği test etmek için MSVC 2019'u kullandım, ancak esas olarak Clang ile çalışıyorum.
keith

İlgili: Neden-i-önlemek-stdenable-eğer-in-işlev-imzalar ve C ++ 20 de kavram ile yeni yollar
getiriyor

@ Jarod42 Kavramlar benim için C ++ 20'den en çok ihtiyaç duyulan şeylerden biri.
val diyor Reinstate Monica

Yanıtlar:


35

Stackoverflow'da yöntem 2'nin daha sık kullanıldığını gördüm, ancak yöntem 1'i tercih ediyorum.

Öneri: yöntem 2'yi tercih edin.

Her iki yöntem de tek işlevlerle çalışır. Sorun, aynı imzalı bir işlevden fazlasına sahip olduğunuzda ve kümenin yalnızca bir işlevini etkinleştirmek istediğinizde ortaya çıkar.

Etkinleştirmek istediğinizi varsayalım foo() sürüm 1, bar<T>()(bir şeymiş gibi davranma constexprfonksiyonu) olduğu trueve foo()ne zaman, sürüm 2 bar<T>()olduğunu false.

İle

template <typename T, typename = std::enable_if_t<true == bar<T>()>>
void foo () // version 1
 { }

template <typename T, typename = std::enable_if_t<false == bar<T>()>>
void foo () // version 2
 { }

belirsizlik nedeniyle bir derleme hatası alıyorsunuz: foo()aynı imzalı iki işlev (varsayılan şablon parametresi imzayı değiştirmez).

Ancak aşağıdaki çözüm

template <typename T, std::enable_if_t<true == bar<T>(), bool> = true>
void foo () // version 1
 { }

template <typename T, std::enable_if_t<false == bar<T>(), bool> = true>
void foo () // version 2
 { }

SFINAE işlevlerin imzasını değiştirdiği için çalışır.

İlişkisiz gözlem: üçüncü bir yöntem de vardır: dönüş türünü etkinleştirme / devre dışı bırakma (tabii ki sınıf / yapı kurucuları hariç)

template <typename T>
std::enable_if_t<true == bar<T>()> foo () // version 1
 { }

template <typename T>
std::enable_if_t<false == bar<T>()> foo () // version 2
 { }

Yöntem 2 olarak, yöntem 3 aynı imzalı alternatif işlevlerin seçimi ile uyumludur.


1
Büyük açıklama için teşekkürler, bundan sonra 2 ve 3 yöntemlerini tercih edeceğim :-)
keith

"varsayılan şablon parametresi imzayı değiştirmez" - varsayılan şablon parametrelerini de kullanan ikinci varyantınızda bunun farkı nedir?
Eric

1
@Eric - Söylemesi kolay değil ... Diğer cevabın bunu daha iyi açıkladığını varsayalım ... SFINAE varsayılan şablon bağımsız değişkenini etkinleştirir / devre dışı bırakırsa, foo()açık bir ikinci şablon parametresi ( foo<double, double>();çağrı) ile çağırdığınızda işlev kullanılabilir kalır . Ve eğer mevcut kalırsa, diğer versiyonda bir belirsizlik var. Yöntem 2 ile SFINAE, varsayılan parametreyi değil, ikinci bağımsız değişkeni etkinleştirir / devre dışı bırakır. Bu yüzden ikinci bir parametreye izin vermeyen bir ikame hatası olduğundan parametreyi açıklamaya çağıramazsınız. Bu yüzden sürüm mevcut değil, bu yüzden belirsizlik yok
max66

3
Yöntem 3, genellikle sembol ismine sızmama avantajına sahiptir. Değişken auto foo() -> std::enable_if_t<...>, işlev imzasını gizlemekten kaçınmak ve işlev bağımsız değişkenlerini kullanmaya izin vermek için genellikle yararlıdır.
Tekilleştirici

@ max66: anahtar nokta, şablon parametresi varsayılanındaki ikame hatasının, parametre sağlanırsa ve herhangi bir varsayılana gerek yoksa bir hata olmamasıdır.
Eric

21

Max66 cevabına ek olarak , yöntem 2'yi tercih etmenin bir başka nedeni, yöntem 1 ile, ikinci şablon argümanı olarak (yanlışlıkla) açık bir tür parametresini geçebilmeniz ve SFINAE mekanizmasını tamamen yenebilmenizdir. Bu, yazım hatası, kopyala / yapıştır hatası veya daha büyük bir şablon mekanizmasında gözetim olarak ortaya çıkabilir.

#include <cstdlib>
#include <type_traits>
#include <iostream>

// NOTE: foo should only accept T=int
template <class T, class = std::enable_if_t<std::is_same_v<T, int>>>
void foo(){
    std::cout << "method 1" << std::endl;
}

int main(){

    // works fine
    foo<int>();

    // ERROR: subsitution failure, as expected
    // foo<double>();

    // Oops! also works, even though T != int :(
    foo<double, double>();

    return 0;
}

Burada canlı demo


İyi bir nokta. Mekanizma ele geçirilebilir.
max66
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.