web-dev-qa-db-ja.com

enable_ifによる部分的なテンプレート関数の特殊化:デフォルトの実装を作成

C++ 11のenable_ifを使用して、デフォルトの実装だけでなく、関数のいくつかの特殊な実装(たとえば、パラメーターのタイプに基づく)を定義したいと思います。それを定義する正しい方法は何ですか?

次の例は、タイプTに関係なく、「汎用」実装が呼び出されるため、意図したとおりに機能しません。

#include <iostream>

template<typename T, typename Enable = void>
void dummy(T t)
{
  std::cout << "Generic: " << t << std::endl;
}


template<typename T, typename std::enable_if<std::is_integral<T>::value>::type>
void dummy(T t)
{
  std::cout << "Integral: " << t << std::endl;
}


template<typename T, typename std::enable_if<std::is_floating_point<T>::value>::type>
void dummy(T t)
{
  std::cout << "Floating point: " << t << std::endl;
}

int main() {
  dummy(5); // Print "Generic: 5"
  dummy(5.); // Print "Generic: 5"
}

私の最小限の例の1つの解決策は、「汎用」実装を整数型または浮動小数点型ではないものとして明示的に宣言することです。

std::enable_if<!std::is_integral<T>::value && !std::is_floating_point<T>::value>::type

私の実際のユースケースには多くの特殊な実装があり、デフォルトの実装では非常に長い(エラーが発生しやすい!)状態を避けたいので、これはまさに避けたいことです。

20
Bruno

機能を部分的に特化することはできません。私はあなたがしたいことは明示的な条件を含むそれらのオーバーロードを好むことだと思いますか?これを実現する1つの方法は、default関数の宣言で可変引数Ellipsisを使用することです。これは、Ellipsis関数の優先度が過負荷解決の順序で低いためです。

#include <iostream>

template<typename T>
void dummy_impl(T t, ...)
{
  std::cout << "Generic: " << t << std::endl;
}


template<typename T, typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
void dummy_impl(T t, int)
{
  std::cout << "Integral: " << t << std::endl;
}


template<typename T, typename std::enable_if<std::is_floating_point<T>::value>::type* = nullptr>
void dummy_impl(T t, int)
{
  std::cout << "Floating point: " << t << std::endl;
}

template <class T>
void dummy(T t) {
   dummy_impl(t, int{});
}

int main() {
  dummy(5); 
  dummy(5.); 
  dummy("abc"); 
}

出力:

Integral: 5
Floating point: 5
Generic: abc

[ライブデモ]

コメントで@doublepが言及している別のオプションは、関数の実装で構造を使用し、それを部分的に特殊化することです。

13
W.F.

私は次のようにタグディスパッチを使用します:

namespace Details
{
    namespace SupportedTypes
    {
        struct Integral {};
        struct FloatingPoint {};
        struct Generic {};
    };


    template <typename T, typename = void>
    struct GetSupportedType
    {
        typedef SupportedTypes::Generic Type;
    };

    template <typename T>
    struct GetSupportedType< T, typename std::enable_if< std::is_integral< T >::value >::type >
    {
        typedef SupportedTypes::Integral Type;
    };

    template <typename T>
    struct GetSupportedType< T, typename std::enable_if< std::is_floating_point< T >::value >::type >
    {
        typedef SupportedTypes::FloatingPoint Type;
    };

    template <typename T>
    void dummy(T t, SupportedTypes::Generic)
    {
        std::cout << "Generic: " << t << std::endl;
    }

    template <typename T>
    void dummy(T t, SupportedTypes::Integral)
    {
        std::cout << "Integral: " << t << std::endl;
    }

    template <typename T>
    void dummy(T t, SupportedTypes::FloatingPoint)
    {
        std::cout << "Floating point: " << t << std::endl;
    }
} // namespace Details

そして、次のようにボイラープレートコードを非表示にします。

template <typename T>
void dummy(T t)
{
    typedef typename Details::GetSupportedType< T >::Type SupportedType;
    Details::dummy(t, SupportedType());
}

GetSupportedTypeは、使用する実際のタイプを推測するための1つの中心的な方法を提供します。これは、新しいタイプを追加するたびに特殊化する方法です。

次に、右のインスタンスを提供することにより、右のdummyオーバーロードを呼び出すだけですタグ

最後に、dummyを呼び出します。

dummy(5); // Print "Generic: 5"
dummy(5.); // Print "Floating point: 5"
dummy("lol"); // Print "Generic: lol"
5
mister why