2012-06-26 8 views
6

std::functionでいくつかの異なる機能を共有するクラスを作成しています(少なくともクラスは多くの点で似ています)。テンプレートパラメータ(つまりstd::function<void (std::string&)>)を指定してstd::functionをインスタンス化することはすべてわかっていますが、これは自分のクラスでも同じです。私は例外がありますが、単一の関数を返す値がvoid(std::function<"return value" ("parameters">)の場合、私のクラスに特化したいと考えています。私はコンパイル時にこれを行う必要があり、私はちょうどそれがうまく動作するようにすることはできません。ここでは説明のためのいくつかのテストコードは次のとおりです。コンパイル時の型の特殊化

#include <iostream> 
#include <type_traits> 

template <typename T> class Test { }; 

template <typename Ret, typename... Args> 
class Test<Ret (Args...)> 
{ 
public: 
    Ret operator()(Args...) 
    { 
     if(std::is_void<Ret>::value) 
     { 
      // Do something... 
     } 

     else /* Not a void function */ 
     { 
      Ret returnVal; 
      return returnVal; 
     } 
    } 
}; 

int main(int argc, char * argv[]) 
{ 
    Test<void (char)> test; 
    test('k'); 
} 

あなたは明確に(すなわちvoid returnVal;)コンパイラは、上記の試験で「他」の枝を削除しない場合は、私のコードは無効値を作成しようと、見ることができるように。問題は、私はコンパイルエラーになってしまうので、コンパイラはブランチを削除しないことです:

./test.cpp: In instantiation of ‘Ret Test::operator()(Args ...) [with Ret = void; Args = {char}]’: ./test.cpp:27:10: required from here ./test.cpp:18:8: error: variable or field ‘returnVal’ declared void ./test.cpp:19:11: error: return-statement with a value, in function returning 'void' [-fpermissive]

一つは、通常std::is_voidと組み合わせるstd::enable_ifを使用することになり、問題は、私はに特化したくないということです機能テンプレートが、クラステンプレートにあります。

template <typename Ret, typename... Args> 
class Test<Ret (Args...)> 
{ 
public: 
    typename std::enable_if<!std::is_void<Ret>::value, Ret>::type 
    Ret operator()(Args...) 
    { 
     Ret returnVal; 
     return returnVal; 
    } 

    typename std::enable_if<std::is_void<Ret>::value, Ret>::type 
    Ret operator()(Args...) 
    { 
     // It's a void function 
     // ... 
    } 
}; 

私が代わりに上記のコードを使用している場合、私はただの馬鹿だ場合、私は申し訳ありませんが、その答えは明白です

./test.cpp:11:2: error: expected ‘;’ at end of member declaration 
./test.cpp:11:2: error: declaration of ‘typename std::enable_if<(! std::is_void<_Tp>::value), Ret>::type Test<Ret(Args ...)>::Ret’ 
./test.cpp:6:11: error: shadows template parm ‘class Ret’ 
./test.cpp:11:24: error: ISO C++ forbids declaration of ‘operator()’ with no type [-fpermissive] 
./test.cpp:18:2: error: expected ‘;’ at end of member declaration 
./test.cpp:18:2: error: declaration of ‘typename std::enable_if<std::is_void<_Tp>::value, Ret>::type Test<Ret(Args ...)>::Ret’ 
./test.cpp:6:11: error: shadows template parm ‘class Ret’ 
./test.cpp:18:24: error: ISO C++ forbids declaration of ‘operator()’ with no type [-fpermissive] 
./test.cpp:18:6: error: ‘int Test<Ret(Args ...)>::operator()(Args ...)’ cannot be overloaded 
./test.cpp:11:6: error: with ‘int Test<Ret(Args ...)>::operator()(Args ...)’ 
./test.cpp: In member function ‘int Test<Ret(Args ...)>::operator()(Args ...)’: 
./test.cpp:22:2: warning: no return statement in function returning non-void [-Wreturn-type] 
./test.cpp: In instantiation of ‘int Test<Ret(Args ...)>::operator()(Args ...) [with Ret = void; Args = {char}]’: 
./test.cpp:28:10: required from here 
./test.cpp:13:7: error: variable or field ‘returnVal’ declared void 
./test.cpp: In member function ‘int Test<Ret(Args ...)>::operator()(Args ...) [with Ret = void; Args = {char}]’: 
./test.cpp:15:2: warning: control reaches end of non-void function [-Wreturn-type] 

さらに多くのエラーが発生して、その溶液ずに終わります。私はかなり新しいテンプレートですが、私は他のスレッド/質問のいずれかで適切な答えを見つけることができませんでした。

+0

最初の例で必要としていることを正確に行う「静的なif」を含める提案があります。 – mfontanini

+0

@mfontanini:2つの提案のいずれかが実際に進むかどうかは別の問題です。 –

+0

"静的なテンプレートを使用してSFINAEを使用すると、コンパイラが分岐を削除しないためコンパイラエラーが発生するという問題があります= >残念なことに、コンパイラ*は*ではないことが要求されます。 –

答えて

7

説明からはっきりしないものがいくつかありますので、最も一般的な回答から始めます。

テンプレートに振る舞いを同じに保つ必要がある他の関数があり、特定の関数の振る舞いだけを再定義したいと仮定すると、最も単純な答えはテンプレートを2つに分割し、継承を使用してマージしますそれら。この時点で、基本テンプレートにテンプレートの部分特殊化を使用することができます。

template <typename T, typename... Args> 
struct tmpl_base { 
    T operator()(Args... args) { 
     //generic 
    } 
}; 
template <typename... Args> 
struct tmpl_base<void,Args...> { 
    void operator()(Args... args) { 
    } 
}; 

template <typename Ret, typename... Args> 
class Test<Ret (Args...)> : tmp_base<Ret,Args...> { 
    // do not declare/define operator(), maybe bring the definition into scope: 
    using tmp_base<Ret,Args...>::operator(); 

    // Rest of the class 

をこれはあなたのテンプレートでのみ機能がある場合には、部分特殊継承を悪用する必要はありませんはるかに簡単な解決策です。

+0

私は原則として、コンポジションが動作するとき(またはここでは自由な機能への委任)、継承を使用することを奨励しますが、それほど簡単ではないと認めなければなりません。 @MatthieuM。 –

+0

:これはおそらく私が必要とする悪であると信じている唯一の相続の濫用です。主な問題は、 'return delegate(args ...) 'という表現は、' void'が返された場合には不正な形式になるためです。この特定の問題は、合成オブジェクトまたは別の関数への委譲をブロックします。 –

+0

私たちはこれを行う必要があると思います。このような単純な作業にどれくらいのコードが必要かは悲しいことです。私はC++に[static if](http://dlang.org/version.html#StaticIfCondition)の:( –

2

1つの解決策は、クラステンプレートを部分的に特化しています。

#include <iostream> 
#include <type_traits> 

template <typename T> class Test { }; 

template <typename Ret, typename... Args> 
class Test<Ret (Args...)> 
{ 
public: 
    Ret operator()(Args...) 
    { 

     std::cout<<"non-void function"<<std::endl; 
     Ret returnVal; 
     return returnVal; 

    } 
}; 

template <typename... Args> 
class Test<void (Args...)> 
{ 
public: 
    void operator()(Args...) 
    { 

     std::cout<<"void function"<<std::endl; 
    } 
}; 


int main(int argc, char * argv[]) 
{ 
    Test<void (char)> test; 
    test('k'); 
}