【问题标题】:c++ Templates to change constness of a functionc ++模板更改函数的常量
【发布时间】:2016-07-04 13:53:19
【问题描述】:

我有兴趣设计一个模板接口,其中函数的常量和返回类型本身会根据模板参数而变化。我已设法为返回类型执行此操作,如下所示。

template<typename T, bool canChange>
struct Changable{};

template<typename T>
struct Changable<T,true>
{
    typedef T type;
};

template<typename T>
struct Changable<T,false>
{
    typedef const T type;
};

template<typename T, bool canChange>
struct Data{
    typedef typename Changable<T,canChange>::type DataType;        
    DataType m_data; //< This makes it const/non-const at compile time.

    // This function will also make the return type const/non-const 
    // at compile time. 
    DataType& GetDataRef(){ return m_data;} 

    //However, it seems to me that I still need a second function 
    //with an explicit "const", which I can't seem to avoid.
    DataType& GetDataRef()const{return m_data;}
};

我可以在编译时使用一些 SFINAE 魔法以某种方式避免在此处使用两个 const/non-const 函数吗? std::enable_if 在这里本来是理想的,但在我看来 const 不是一种类型,这种方法可能行不通。有什么建议吗?

【问题讨论】:

  • 如果不需要重载,为什么不只保留const 版本?
  • @KABoissonneault 我确实需要这两个版本,因为模板的常量会根据模板参数而改变。澄清一下,我确实希望能够创建某些 Data 结构,这些结构可以在以后修改,而有些则保证不会改变。因此需要 const/non-const 功能。

标签: c++ templates c++11 sfinae


【解决方案1】:

下面是一个基于继承的例子:

#include <type_traits>
#include <iostream>

template<typename T, bool canChange>
struct Changable { using type = const T; };

template<typename T> 
struct Changable<T, true> { using type = std::decay_t<T>; };

template<typename, typename, bool>
struct Base;

template<typename D, typename T>
struct Base<D, T, true> {
    using DataType = typename Changable<T, true>::type;
    DataType& GetDataRef() { std::cout << "non-const" << std::endl; return static_cast<D*>(this)->m_data; }
};

template<typename D, typename T>
struct Base<D, T, false> {
    using DataType = typename Changable<T, false>::type;
    DataType& GetDataRef() const { std::cout << "const" << std::endl; return static_cast<const D*>(this)->m_data; }
};

template<typename T, bool canChange>
struct Data: Base<Data<T, canChange>, T, canChange> {
    friend class Base<Data<T, canChange>, T, canChange>;
    typename Base<Data<T, canChange>, T, canChange>::DataType m_data{};
    using Base<Data<T, canChange>, T, canChange>::GetDataRef;
};

int main() {
    Data<int, true> d1;
    Data<int, false> d2;
    d1.GetDataRef();
    d2.GetDataRef();
}

根据要求,Data 只有一个 GetDataRef 方法的定义。
哪一个可用,const 一个或另一个,取决于canChange 的值。

注意friend 声明。它允许基类访问Data 的私有数据成员。

【讨论】:

  • 不错的一个!一如既往的无与伦比! :)
  • @W.F.老实说,您的回答指出了正确的出路。 ;-)
  • 我喜欢这个:)。谢谢!
  • @cplusplusrat 欢迎您。挑战总是好的,因为我通常会从他们身上学到一些东西。 ;-)
  • @skypjack 不需要继承。 ;-)
【解决方案2】:

我想我会使用标准库中已有的模板来解决这个问题。它不需要继承或任何自定义类。

#include <utility>

template<typename T, bool canChange>
struct Data{
    using value_type = T;
    using cv_type = std::conditional_t<canChange, value_type, std::add_const_t<value_type>>;
    using reference = std::add_lvalue_reference_t<cv_type>;
    using const_reference = std::add_lvalue_reference_t<std::add_const_t<cv_type>>;

    Data(T t) : m_data(std::move(t)) {}

    cv_type m_data; //< This makes it const/non-const at compile time.

    // This function will also make the return type const/non-const
    // at compile time.
    reference GetDataRef(){ return m_data;}

    //However, it seems to me that I still need a second function
    //with an explicit "const", which I can't seem to avoid.
    const_reference GetDataRef() const {return m_data;}
};

int main()
{
    Data<int, true> d1 { 10 };
    d1.m_data = 12;
    const Data<int, true>& rd1 = d1;

    auto& a = d1.GetDataRef();
    auto& b = rd1.GetDataRef();
    a = 12;  // compiles fine
//    b= 12; won't compile

    Data<int, false> d2 { 10 };
    const Data<int, false>& rd2 = d2;

    auto& c = d2.GetDataRef();
    auto& d = rd2.GetDataRef();
//    c = 12;  // won't compile
//    d = 12;  // won't compile

}

现在问题来了:

我能否以某种方式避免在编译时使用两个 const/non-const 函数使用一些 SFINAE 魔法

您几乎在这里回答了您自己的问题。 SFINAE 要求在直接上下文中考虑模板参数。这是一种复杂的说法,std::enable_if&lt;&gt; 中的表达式必须依赖于某些模板类型。

不幸的是,T 的模板类型在函数 GetDataRef 被计算时就已经知道了,所以 enable_if 在这里对我们没有帮助。

因此,如果我们只想要GetDataRef 的一个版本,我们确实不得不求助于从模板类型派生(然后将在 T 的直接上下文中评估基类)。

然而,即使那样也有问题。

考虑:

  • Data&lt;int, true&gt;&amp; x这是对包含可变数据的可变容器的引用

  • const Data&lt;int, true&gt;&amp; y这是对包含可变数据的不可变容器的引用

调用 x.GetDataRef() 应该返回一个对 int 的可变引用,否则我们会迷惑我们的用户。

调用y.GetDataRef() 肯定会返回一个对int 的const 引用,否则,用户可能会再次震惊地得知const 事物的成员实际上是可变的。

【讨论】:

  • 起首...无论如何,OP 只需要其接口中的一个函数,即 const 或非 const(它取决于 canChange)。这样,您就有了GetDataRef 的两个定义。 (要明确一点,我同意你的看法,但我不知道真正的问题是什么,所以我无法判断)。
  • @skypjack 我知道他认为他想要那个,但我认为他没有考虑清楚。答案已更新。
  • ... 我知道他认为他想要那个 ...你赢了! :-D
【解决方案3】:

也许这样的事情可以解决这个问题:

#include <type_traits>
#include <iostream>

template<typename T, bool canChange>
struct Changable: std::false_type { using type = const T; };

template<typename T>
struct Changable<T, true>: std::true_type { using type = std::decay_t<T>; };

template<typename T, bool canChange>
struct Data {
    using DataTraits = Changable<T, canChange>;

private:
    template<typename U>
    std::enable_if_t<U::value, typename U::type&>
    GetDataRefImpl() { std::cout << "non const" << std::endl; return m_data; }

    template<typename U>
    std::enable_if_t<not U::value, typename U::type&>
    GetDataRefImpl() const { std::cout << "const" << std::endl; return m_data; }

public:
    typename DataTraits::type m_data{};

    typename DataTraits::type& GetDataRef() { return GetDataRefImpl<DataTraits>(); }
    typename DataTraits::type& GetDataRef() const { return GetDataRefImpl<DataTraits>(); }
};

int main() {
    Data<int, true> d1;
    Data<int, false> d2;
    d1.GetDataRef();
    d2.GetDataRef();
}

基本思想是让两个函数都由类公开,然后在内部将它们转发到相同的 sfinaed 一个 constnon-const(这取决于canChange)。
运行示例可以看到,结果是:

非常量
常量

即使d1d2 都被定义为非常量也是如此。
std::enable_if在编译时开启正确的内部函数。

请注意,我使用了 C++14 提供的功能(例如 std::enable_if_t)。
该示例可以很容易地转换为基于(std::enable_if_t 无非是typename std::enable_if&lt;condition, type&gt;::type 等等)的C++11。

【讨论】:

  • 您仍然需要公共接口中的 const 和 non-const 函数。它只比我已经拥有的好一点。我一直在寻找类似的问题,现在我越来越相信如果没有公共界面中的两个功能,它就无法完成。
  • @cplusplusrat 你是对的,但这些方法只不过是内部 sfinaed 方法的转发器。此外,它们允许您将该方法用于 const 和非 const 实例。你的类的常量现在被保留了。另一种解决方案是在 per-canChange 基础上专门化该类,或者有一个委托来转发请求。
【解决方案4】:

如果您可以使用 C++17,请查看 is_constadd_constremove_const。 与if constexpr () 一起,一个相当优雅的解决方案应该是可能的。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-10-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多