2017-01-31 9 views
12

私の実際の例はかなり大きいので、私は単純化したものを使用します。ポインタ、値、スマートポインタのためのC++テンプレートの統一

struct Rectangle { 
    int width; 
    int height; 

    int computeArea() { 
    return width * height; 
    } 
} 

そして例えば、そのタイプを消費し、別の種類::私は長方形のデータ型があると今

struct TwoRectangles { 
    Rectangle a; 
    Rectangle b; 
    int computeArea() { 
    // Ignore case where they overlap for the sake of argument! 
    return a.computeArea() + b.computeArea(); 
    } 
}; 

を、私はのユーザーに所有権の制約を入れたくありませんTwoRectanglesので、私はそれのテンプレートしたいと思います:

template<typename T> 
struct TwoRectangles { 
    T a; 
    T b; 
    int computeArea() { 
    // Ignore case where they overlap for the sake of argument! 
    return a.computeArea() + b.computeArea(); 
    } 
}; 

用途:

TwoRectangles<Rectangle> x; 
TwoRectangles<Rectangle*> y; 
TwoRectangles<std::shared_ptr<Rectangle>> z; 
// etc... 

問題は、発信者は、ポインタを使用したい場合は、関数の本体が異なるということです。

template<typename T> 
struct TwoRectangles { 
    T a; 
    T b; 
    int computeArea() { 
    assert(a && b); 
    return a->computeArea() + b->computeArea(); 
    } 
}; 

コードのmaxiumum量があるように私のテンプレート機能を統合する最良の方法は何ですかポインタ、値、スマートポインタのために再利用されますか?それはあなたがそれらの式の両方が有効であるのためのタイプがあるでしょうそうだ

template<typename T> 
struct TwoRectangles { 
    T a; 
    T b; 

    int computeArea() { 
    return areaOf(a) + areaOf(b); 
    } 

private: 
    template <class U> 
    auto areaOf(U& v) -> decltype(v->computeArea()) { 
     return v->computeArea(); 
    } 

    template <class U> 
    auto areaOf(U& v) -> decltype(v.computeArea()) { 
     return v.computeArea(); 
    } 
}; 

+7

あなたは適用できないものを除外するためにSFINAEで過負荷の束を書くことができます。または、必要なものだけを処理するようにデザインを変更し、実際に必要になるまで必要と思われるものを除外することができます。 –

+4

' - >'のみを使用してください。カスタムポインタのようなオブジェクトにポインタ以外の値をラップします。 –

+1

あなたは標準のライブラリを何かすることができ、追加のテンプレートタイプを与えることができます。これは "Tをどうやって取得するか"を知っていて、 'computeArea'を呼び出していることです。それとも、あまりにもそれをエンジニアリングしないでください。 :) – GManNickG

答えて

21

一つ、これをやってTwoRectangles内のすべてをカプセル化する方法は、何かのようになります。しかし、あなたはいつも第二の引数を持つさらなる曖昧さ回避をareaOf()に加えることができます。 std::invoke()


もう一つの方法は、すでに何でも上の機能を呼び出すの標準ライブラリの方法があるという事実を利用することであろう。 (それは大きな変化だから)、私は実際にいくつかの時間前に同様の問題があった

template <class T, class = void> 
struct element_type { 
    using type = T; 
}; 

template <class T> 
struct element_type<T, void_t<typename std::pointer_traits<T>::element_type>> { 
    using type = typename std::pointer_traits<T>::element_type; 
}; 

template <class T> 
using element_type_t = typename element_type<T>::type; 

template<typename T> 
struct TwoRectangles { 
    T a; 
    T b; 

    int computeArea() { 
    using U = element_type_t<T>; 
    return std::invoke(&U::computeArea, a) + 
     std::invoke(&U::computeArea, b); 
    } 
}; 
2

最終的に私は今のためにそれをしない選択しました:あなたは基になる型を知っておく必要がありますそれは正しいと思われる解決法を生み出しました。

間接指定がある場合、基礎となる値にアクセスするためのヘルパー関数を作成することを考えました。コードでは、このようになります。また、あなたのような例もあります。

#include <iostream> 
#include <string> 
#include <memory> 

namespace detail 
{ 
    //for some reason the call for int* is ambiguous in newer standard (C++14?) when the function takes no parameters. That's a dirty workaround but it works... 
    template <class T, class SFINAE = decltype(*std::declval<T>())> 
    constexpr bool is_indirection(bool) 
    { 
     return true; 
    } 
    template <class T> 
    constexpr bool is_indirection(...) 
    { 
     return false; 
    } 
} 
template <class T> 
constexpr bool is_indirection() 
{ 
    return detail::is_indirection<T>(true); 
} 

template <class T, bool ind = is_indirection<T>()> 
struct underlying_type 
{ 
    using type = T; 
}; 

template <class T> 
struct underlying_type<T, true> 
{ 
    using type = typename std::remove_reference<decltype(*(std::declval<T>()))>::type; 
}; 

template <class T> 
typename std::enable_if<is_indirection<T>(), typename std::add_lvalue_reference<typename underlying_type<T>::type>::type>::type underlying_value(T&& val) 
{ 
    return *std::forward<T>(val); 
} 

template <class T> 
typename std::enable_if<!is_indirection<T>(), T&>::type underlying_value(T& val) 
{ 
    return val; 
} 
template <class T> 
typename std::enable_if<!is_indirection<T>(), const T&>::type underlying_value(const T& val) 
{ 
    return val; 
} 


template <class T> 
class Storage 
{ 
public: 
    T val; 
    void print() 
    { 
     std::cout << underlying_value(val) << '\n'; 
    } 
}; 

template <class T> 
class StringStorage 
{ 
public: 
    T str; 
    void printSize() 
    { 
     std::cout << underlying_value(str).size() << '\n'; 
    } 
}; 

int main() 
{ 
    int* a = new int(213); 
    std::string str = "some string"; 
    std::shared_ptr<std::string> strPtr = std::make_shared<std::string>(str); 
    Storage<int> sVal{ 1 }; 
    Storage<int*> sPtr{ a }; 
    Storage<std::string> sStrVal{ str }; 
    Storage<std::shared_ptr<std::string>> sStrPtr{ strPtr }; 
    StringStorage<std::string> ssStrVal{ str }; 
    StringStorage<const std::shared_ptr<std::string>> ssStrPtr{ strPtr }; 

    sVal.print(); 
    sPtr.print(); 
    sStrVal.print(); 
    sStrPtr.print(); 
    ssStrVal.printSize(); 
    ssStrPtr.printSize(); 

    std::cout << is_indirection<int*>() << '\n'; 
    std::cout << is_indirection<int>() << '\n'; 
    std::cout << is_indirection<std::shared_ptr<int>>() << '\n'; 
    std::cout << is_indirection<std::string>() << '\n'; 
    std::cout << is_indirection<std::unique_ptr<std::string>>() << '\n'; 
} 
+1

だからあなたのdownvote?私が行方不明であることを教えてください、私は喜んで学ぶでしょう – Sopel

+1

これにはいくつかの問題があります。 1) 'T *'と 'T const *'のオーバーロードは冗長です。 2) 'T&'オーバーロードは重大であり、 'T&'オーバーロードでは危険です。なぜなら、あなたが値を渡すと、手間のかかる参照が出るからです。3)これはスマートポインタをサポートしていません。 – Barry

+0

@バリーありがとう。私はconst&overloadなしで試しましたが、const変数(おそらく私は間違ったことをしていたかもしれません)と一緒に作業していませんでしたが、これは非常に重要だと思います。そして、私はそれが危険であることに同意しますが、価値が渡される可能性があるので、そのようなセマンティクスを変更するという考え方全体は危険です。私はそれを残しました。コードに変更を加えたので、スマートポインタを扱うようになりました。それは可能なはずですが、今はあまり時間がありません。それは列挙型用標準ライブラリで使用されているので – Sopel

関連する問題