简体   繁体   English

统一指针,值和智能指针的C ++模板

[英]Unify C++ templates for pointers, values and smart pointers

My real example is quite big, so I will use a simplified one. 我的真实例子非常大,所以我将使用简化的例子。 Suppose I have a data-type for a rectangle: 假设我有一个矩形的数据类型:

struct Rectangle {
  int width;
  int height;

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

And another type that consumes that type, for example: 另一种消耗该类型的类型,例如:

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

Now, I don't want to put ownership constraints on users of TwoRectangles , so I would like to make it a template: 现在,我不想对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();
  }
};

Usages: 用途:

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

The problem is that if the caller wants to use pointers, the body of the function should be different: 问题是如果调用者想要使用指针,函数的主体应该是不同的:

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

What is the best way of unifying my templated function so that the maxiumum amount of code is reused for pointers, values and smart pointers? 统一我的模板化函数的最佳方法是什么,以便最大量的代码重用于指针,值和智能指针?

One way of doing this, encapsulating everything within TwoRectangles , would be something like: 这样做的一种方法是封装TwoRectangles所有TwoRectangles ,如下所示:

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();
    }
};

It's unlikely you'll have a type for which both of those expressions are valid. 您不太可能拥有这两个表达式都有效的类型。 But you can always add additional disambiguation with a second argument to areaOf() . 但是你总是可以使用第二个参数向areaOf()添加额外的消歧。


Another way, would be to take advantage of the fact that there already is a way in the standard library of invoking a function on whatever: std::invoke() . 另一种方法是利用以下事实:在标准库中已经有一种方法可以调用函数: std::invoke() You just need to know the underlying type: 您只需要知道基础类型:

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;

and

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);
  }
};

I actually had a similar problem some time ago, eventually i opted not to do it for now (because it's a big change), but it spawned a solution that seems to be correct. 实际上我前段时间遇到了类似的问题,最终我选择不这样做(因为这是一个很大的变化),但它产生了一个似乎是正确的解决方案。

I thought about making a helper function to access underlying value if there is any indirection. 如果有任何间接,我想过制作一个帮助函数来访问底层值。 In code it would look like this, also with an example similar to yours. 在代码中它看起来像这样,也有一个类似于你的例子。

#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';
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM