【问题标题】:How can I design around this restriction on concepts and incomplete types?我该如何围绕这个对概念和不完整类型的限制进行设计?
【发布时间】:2021-07-17 22:05:42
【问题描述】:

我有这样的事情:

template <typename T>
requires ::std::movable<T> || ::std::copyable<T> || ::std::is_void_v<T>
class ValueWrapper
{
   // various functions to do stuff with value
};

class Value {
 public:
    Value(Value const &) = delete;
    Value &operator =(Value const &) = delete;
    Value(Value &&other) noexcept : x_{other.x_} { other.x_ = -1; }
    Value &operator =(Value &&other) {
        Value tmp{::std::move(other)};
        x_ = tmp.x_;
        tmp.x_ = -1;
        return *this;
    }

ValueWrapper<Value> do_something() const;  // Generates error related to incomplete type. :-(

 private:
    int x_;
};

当然,它不起作用,因为在编译器看到 do_something 声明时,Value 是一个不完整的类型,并且不可能测试不竞争的类型是否可移动或可复制,并且它不是无效的。

我应该如何设计?

我可以更改 ValueWrapper,以便各个成员函数需要一些东西,而不是让类需要一些模板参数。但是,这似乎很迟钝,因为该类的目的是包装可移动或可复制的东西。我可以将do_something 移出Value 类并使其成为免费功能。但这似乎过于约束,对于任何Value 类的方法来说可能不是明智之举。

这里还有其他没有这些缺点的设计选择吗?

在我目前关注的特定非抽象案例中,ValueWrapper 恰好类似于 Boost expected,因此被用于表示错误或返回值。

编辑:到目前为止,我最喜欢的答案是使用auto,并且要求函数定义内联显示才能工作。如果你希望函数定义是内联的,你可以做这些体操。但是,这真的很奇怪。

#include <concepts>
#include <utility>


template <typename T>
requires ::std::movable<T> || ::std::copyable<T> || ::std::is_void_v<T>
class ValueWrapper
{
   // various functions to do stuff with value
};

namespace priv_ {
    // We can forward declare a class without its member functions.
    // But we can't forward declare a function without being able to
    // fully name all of its types.
    class Silly;
}

class Value {
 public:
    Value() : x_{-1} { }
    Value(Value const &) = delete;
    Value &operator =(Value const &) = delete;
    Value(Value &&other) noexcept : x_{other.x_} { other.x_ = -1; }
    Value &operator =(Value &&other) {
        Value tmp{::std::move(other)};
        x_ = tmp.x_;
        tmp.x_ = -1;
        return *this;
    }

    // inline here is basically documenting that we intend to give an
    // inline definition later. We might want to say what the actual
    // return type is too.
    auto inline do_something() const;

 private:
    int x_;

    // And we can friend a forward declared class so all of its member
    // functions are basically member functions of this class (and
    // hence have unrestricted access to all member functions and
    // variables).
    friend class priv_::Silly;
};

namespace priv_ {
    class Silly {
     public:
        // And finally, now that the Value type is 'complete', we can
        // use it as a parameter to the `ValueWrapper` template type.
        static ValueWrapper<Value> p_do_something(Value const &v);
    };
}

auto inline Value::do_something() const
{
    // And now that the declaration for `p_do_something` has been
    // seen, we can call it.
    return priv_::Silly::p_do_something(*this);
}

ValueWrapper<Value> foo()
{
    Value v;
    return v.do_something();
}

【问题讨论】:

  • 您希望do_something() 链接,还是返回具有大部分/所有相同功能的基类?
  • @MooingDuck - 你所说的“链”是什么意思?
  • a.doSomething().doSomething().doSomething()。这是最常见的operator+
  • .result(),当使用类似expected的类型时。是的,链接很重要。
  • 无关:copyable 暗示 movable,所以 || copyable&lt;T&gt; 是多余的。

标签: c++ c++20 c++-concepts incomplete-type


【解决方案1】:

在模板方法中用默认类型转换do_something ()怎么样?

我的意思是

template <typename U = Value>
ValueWrapper<U> do_something() const;

也可以

template <int..., typename U = Value>
ValueWrapper<U> do_something() const;

如果你想避免 do_something() 可以被称为解释类型。

【讨论】:

  • 有趣。我没想到。
【解决方案2】:

类型在成员函数定义中是完整的,所以我们可以使用推导的返回类型:

auto do_something() const {
    return ValueWrapper<Value>();
}

【讨论】:

  • 这看起来很有希望。
  • 我对此进行了测试。这几乎是完美的解决方案。避免了几乎所有的缺点,并且只引入了它自己的一个很小的缺点,可以通过注释或具有非常简单和明显的内联指定函数体来轻松解决。但有一个主要缺点。如果函数非常复杂并且需要在它自己的.cpp 文件中,这将不起作用。有几种方法可以解决这个问题。一方面,您可以在 priv_ 命名空间中有一个免费函数,您可以调用它来完成所有工作,因此接口仍然可以让该函数显示为成员函数。
  • 进一步澄清。 priv_ 技巧需要一些有趣的技巧,包括在类主体中声明内联函数,然后在看到 priv_ 命名空间中的声明之后,在类主体外部定义它。而且它不能成为朋友,因为将其声明为朋友需要一个引入相同问题的前向声明。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-24
  • 2017-06-11
  • 2021-08-29
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多