2016-11-14 3 views
4

しばらくこのようなコードを使用してきた(以降GCC 4.9/3.5クラン少なくとも):現在のクラスで利用可能なオーバーロードに基づいてSFINAEへの道はありますか?

Rでない限り、第二 bar()過負荷の点は、それが離れてSFINAE'dされるべきさ
#include <utility> 

class foo 
{ 
public: 
    void bar(int n); 

    template <typename R, 
       typename = decltype(std::declval<foo>().bar(*std::begin(std::declval<R>())))> 
    void bar(const R& range); 
}; 

その要素にはbar()の過負荷が存在する範囲タイプです。したがってstd::vector<int>は問題ありませんが、std::vector<int*>はそうではありません。

は残念ながら、クラン3.9以降、それはこのエラーを与える:

templ.cpp:12:54: error: member access into incomplete type 'foo' 
       typename = decltype(std::declval<foo>().bar(*std::begin(std::declval<R>())))> 
                ^
templ.cpp:6:7: note: definition of 'foo' is not complete until the closing '}' 
class foo 
    ^
1 error generated. 

は、独自の定義の中から、不完全な型を使用してに依存しない、これを達成する方法はありますか?

+2

クラスは関数のデフォルト引数内で完全なので、SFINAEをデフォルト引数を持つ関数パラメータに移動できます。 – bogdan

+0

目標は、そのテンプレートをあまりにも積極的に一致させることを止めることでした。具体的な例として 'std :: string'は' char'sのコンテナですが、 'char'のオーバーロードがないので、テンプレートはスキップされ、代わりに' string_view'オーバーロードが選択されます。 SFINAEがなければ、テンプレートを選択してからエラーになります。 –

+0

'std :: vector >'が正しく動作しますか?次に、あなたは '*'が欠けているように見えます。 – Yakk

答えて

4

たぶん、あなたはfooの追加テンプレートパラメータのデフォルト値は作ることができます:fooが完了した場合、これはチェックを遅らせる

#include <utility> 

class foo 
{ 
public: 
    void bar(int n); 

    template <typename R, 
       typename F = foo, 
       typename = decltype(std::declval<F>().bar(*std::begin(std::declval<R>())))> 
    void bar(const R& range); 
}; 

[live demo]

を。

+1

誰か型名 'F'の値を上書きすることができます。あなたはそのためにちょうどその前にvariadicを導入することができます。 –

+0

@GuillaumeRacicot良い点! –

2

barを基本クラスに定義するのが、簡単で簡単な方法です。

#include <utility> 

template<typename child> 
struct base { 
    void bar(int); 
}; 

struct foo : base<foo> { 
    template<typename R, 
       typename = decltype(std::declval<base<foo>>().bar(std::begin(std::declval<R>())))> 
    void bar(const R& range); 
}; 

しかし、この方法は面倒なことがあります。

代わりにあなたはどのようなタイプbar必要性を知っていれば、あなたはこれを行うことができますで

struct foo { 
    template<typename T, std::enable_if_t<some_contraint<T>::value>* = 0> 
    void bar(T); 

    template<typename R, 
     std::enable_if_t<some_contraint<*std::begin(std::declval<R>())>::value>* = 0> 
    void bar(const R& range); 
}; 

:制約に限定されている

struct foo { 
    void bar(int); 

    template<typename R, 
     std::enable_if_t<std::is_constructible<int, decltype(*std::begin(std::declval<R>()))>>* = 0> 
    void bar(const R& range); 
}; 

bar場合、あなたは非常に同じ制約を使用することができます最後に、最後の2つのオプションが好きな場合は、型制約に範囲制約をカプセル化できます。

template<typename, typename = void> 
struct is_valid_range : std::false_type {}; 

template<typename T> 
struct is_valid_range<T, std::enable_if_t<some_contraint<*std::begin(std::declval<R>())>::value>> : std::true_type {}; 
1

関数のボディがコンパイルされない場合、オーバーロードが選択されないようにしようとしているようです。問題は、コンパイラが署名がボディに移動する前にコンパイルされていることを確認する必要があることです。

代わりに、Rと具体的に何ができるのかに基づいてSFINAEを実行するのはどうですか?たとえば、次のように

template<typename R, 
     class = decltype(begin(std::declval<const R&>())), 
     class = decltype(end(std::declval<const R&>()))> 
void bar(const R& range); 

あなたはconst R& F型にbeginendを呼び出すことができる場合は、この過負荷にのみ選択される方法。

+0

あなたは私がやろうとしていることについて正しいです。関数の本体は 'for(auto e:range)bar(e); 'のようなものなので' R'がレンジ型であるかどうかだけでなく 'bar(e)'が有効かどうかチェックする必要があります。 –

+0

@TavianBarnesそして、型は 'bar(e) 'を呼び出すことができる必要がありますか?あなたは 'R'のイテレータ型でこれらの制約をチェックすることができます。 – SirGuy

+0

制約は、その型の 'bar'オーバーロードが' foo'に存在するかどうかだけです。 –

1
class Foo; 
void free_bar(Foo* foo, int n){ 
    (void)foo; 
    std::cout << n << "\n"; 
} 

class Foo { 
public: 
    template<class X> 
    void bar(X&& x) { 
    return free_bar(this, std::forward<X>(x)); 
    } 
}; 

template <typename R> 
auto free_bar(Foo* foo, const R& range) 
-> decltype(free_bar(foo, *std::begin(range))) 
{ 
    for (auto&&x:range) 
    free_bar(foo, decltype(x)(x)); 
} 

これは彼らの最初の引数としてFoo*を取る自由な関数にbarを配置します。

メンバー.bar(X)がこのフリー機能を呼び出します。

通常、ADLは正しいこと(tm)を実行することを意味します。

live example

関連する問題