推导模板化类参数的模板参数:const issue

时间:2019-05-29 20:43:23

标签: c++ templates const template-deduction

我认为这个问题相当普遍,因此应该有一个已知的解决方案。我想出了一个,但我并不真正满意,所以我在这里问,希望有人可以提供帮助。

说我有一个函数,其签名是

template<typename T>
void foo(const MyArray<const T>& x);

template参数中的const可以防止更改数组内容,因为(出于此问题之外的原因)[]的访问者(()MyArray<T>)是始终标记为const,并返回对T的引用(因此,const确保安全,因为MyArray<T>::operator[]返回T&,而MyArray<const T>::operator[]返回const T&)。

太好了。但是,具有不同模板参数的模板是无关的,因此我无法将对MyClass<T>的引用绑定到MyClass<const T>的引用,这意味着我不能这样做

MyArray<double> ar(/*blah*/);
foo(ar);

请注意,在没有引用的情况下,上面的代码将可以正常工作,前提是存在一个复制构造函数,该复制构造函数使我可以从MyArray<const T>创建MyArray<T>。但是,我不想删除引用,因为数组构造会很多次,并且尽管相对便宜,但其成本会增加。

问题是:如何用foo来呼叫MyArray<T>

到目前为止,我唯一的解决方法是:

MyArray<T> ar(/*blah*/);
foo(reinterpret_cast<MyArray<const T>>(ar));

(实际上,在我的代码中,我将重新解释转换隐藏在具有更详细名称的内联函数中,但最终结果是相同的)。类MyArray没有针对const T的专门化,因此无法重新解释,因此强制类型转换应为“安全”。但这并不是一个很好的阅读解决方案。一种替代方法是复制foo的签名,使版本采用MyArray<T>,该实现将进行强制转换并调用const版本。这样做的问题是代码重复(而且我有很多函数foo需要重复)。

也许在foo签名上有一些额外的模板魔术?目标是同时传递MyArray<T>MyArray<const T>,同时仍保持const正确性(即,如果我不小心更改了函数体中的输入,会使编译器吠叫)。

编辑1 :类MyArray(其实现不受我的控制)具有const访问器,因为它存储了指针。因此,调用v[3]将修改数组中的值,但不会修改存储在类中的成员(即指针和一些类似于智能指针的元数据)。换句话说,尽管数组实际上是对象,但访问者实际上并未修改该对象。这是语义上的区别。不知道为什么他们要朝这个方向发展(我有一个主意,但是解释的时间太长了。)

编辑2 :我接受了两个答案之一(尽管它们有些相似)。我不确定(出于长期解释的原因)在我的情况下包装类是否可行(也许我必须考虑一下)。

template<typename T>
void foo(const MyArray<const T>& x);
MyArray<int> a;
foo(a);

不编译,以下代码可以编译

void foo(const MyArray<const int>& x);
MyArray<int> a;
foo(a);

注意:MyArray确实提供了带有签名的模板化“复制构造函数”

template<typename S>
MyArray(const MyArray<S>&);

因此它可以从MyArray<const T>创建MyArray<T>。我感到困惑的是,为什么在T是显式的情况下它起作用,而如果T是模板参数却不起作用。

3 个答案:

答案 0 :(得分:3)

我会留在

template<typename T>
void foo(const MyArray<T>&);

,并确保使用const T实例化它(例如在unitTest中)。

否则,您可以将视图创建为std::span

类似的事情(取决于MyArray提供的其他方法,您可能可以做一个更好的const视图。我目前仅使用operator[]):

template <typename T>
struct MyArrayConstView
{
    MyArrayConstView(MyArray<T>& array) : mArray(std::ref(array)) {}
    MyArrayConstView(MyArray<const T>& array) : mArray(std::ref(array)) {}

    const T& operator[](std::size_t i) {
        return std::visit([i](const auto& a) -> const T& { return a[i]; }), mArray);
    }

private:
    std::variant<std::reference_wrapper<MyArray<T>>,
                 std::reference_wrapper<MyArray<const T>>> mArray;
};

然后

template <typename T>
void foo(const MyArrayConstView<T>&);

但是您需要显式地调用它(因为扣除不会发生,因为MyArray<T>不是MyArrayConstView

MyArray<double> ar(/*blah*/);
foo(MyArrayConstView{ar});
foo<double>(ar);

答案 1 :(得分:2)

由于不允许更改MyArray,因此一种选择是使用适配器类。

template <typename T>
class ConstMyArrayView {
   public:
    // Not an explicit constructor!
    ConstMyArrayView(const MyArray<T>& a) : a_(a) {}
    const T& operator[](size_t i) const { return a_[i]; }
   private:
     const MyArray<T>& a_;
};

template<typename T>
void foo(const ConstMyArrayView<T>& x);

MyArray<T> x;
foo(x);

但是最后,如果您可以更改MyArray来匹配所需的const正确性,或者切换到具有此类的类,那将是更好的选择。

答案 2 :(得分:2)

这是让函数使用一种类型的一种丑陋但有效的方法,但是还可以让编译器检查如果使用不同类型的代码,则同一代码是否可以编译:

template <typename From, typename To>
struct xfer_refs_cv
{
    using type = To;
};
template <typename From, typename To>
struct xfer_refs_cv<const From, To>
{
    using type = const typename xfer_refs_cv<From, To>::type;
};
template <typename From, typename To>
struct xfer_refs_cv<volatile From, To>
{
    using type = volatile typename xfer_refs_cv<From, To>::type;
};
template <typename From, typename To>
struct xfer_refs_cv<From&, To>
{
    using type = typename xfer_refs_cv<From, To>::type&;
};
template <typename From, typename To>
struct xfer_refs_cv<From&&, To>
{
    using type = typename xfer_refs_cv<From, To>::type&&;
};

template <typename CheckType, typename Func, typename CallType>
constexpr decltype(auto) check_and_call(Func&& f, CallType&& call_arg)
    noexcept(noexcept(std::forward<Func>(f)(std::forward<CallType>(call_arg))))
{
    (void) decltype(std::declval<Func&&>()
      (std::declval<typename xfer_refs_cv<CallType&&, CheckType>::type>()), 0){};
    return std::forward<Func>(f)(std::forward<CallType>(call_arg));
}

template<typename T>
void foo(const MyArray<T>& x)
{
    check_and_call<MyArray<const T>>(
        [](auto&& x) {
        // Function implementation here.
    }, x);
}
相关问题