c ++模板来更改函数的constness
c++ Templates to change constness of a function
我有兴趣设计一个模板接口,其中函数的常量和 return 类型本身会根据模板参数而变化。我已经设法为 return 类型执行了如下操作。
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 不是一种类型,这种方法可能行不通。有什么建议吗?
也许这样可以解决问题:
#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();
}
基本思想是让 class 公开两个函数,然后在内部将它们转发给同一个 sfinaed,即 const
或 non-const
(这取决于 canChange
的值)。
正如您通过 运行 示例所见,结果是:
non const
const
即使 d1
和 d2
都被定义为非常量也是如此。
std::enable_if
在编译时打开正确的内部函数。
请注意,我使用了 C++14 提供的功能(例如 std::enable_if_t
)。
该示例可以很容易地转换为基于(std::enable_if_t
无非是 typename std::enable_if<condition, type>::type
等等)的 C++11。
下面是一个基于继承的例子:
#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
声明。它允许基 class 访问 Data
.
的私有数据成员
我想我会使用标准库中已有的模板来解决这个问题。它不需要继承或任何自定义 classes.
#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
}
现在回答问题:
Can I somehow avoid having two const/non-const functions here at compile time using some SFINAE magic?
你在这里几乎是在回答你自己的问题。 SFINAE 要求在直接上下文中考虑模板参数。这是表达 std::enable_if<>
中的表达式必须依赖于某种模板类型的复杂方式。
不幸的是,T 的模板类型在计算函数 GetDataRef
时已知,因此 enable_if 在这里帮不了我们。
因此,如果我们只想要 GetDataRef
的一个版本,我们确实必须求助于从模板类型派生(然后将在 T 的直接上下文中评估基础 class)。
但是,即使这样也有问题。
考虑:
Data<int, true>& x
这是对包含可变数据的可变容器的引用
const Data<int, true>& y
这是对包含可变数据的不可变容器的引用
调用 x.GetDataRef()
应该 return 对 int 的可变引用,否则我们会让用户感到困惑。
调用 y.GetDataRef()
肯定 return 对 int 的 const 引用,否则,用户可能会再次震惊地发现 const 的成员实际上是可变的。
如果您可以使用 C++17,请查看 is_const
、add_const
和 remove_const
。
与 if constexpr ()
一起,一个相当优雅的解决方案应该是可能的。
我有兴趣设计一个模板接口,其中函数的常量和 return 类型本身会根据模板参数而变化。我已经设法为 return 类型执行了如下操作。
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 不是一种类型,这种方法可能行不通。有什么建议吗?
也许这样可以解决问题:
#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();
}
基本思想是让 class 公开两个函数,然后在内部将它们转发给同一个 sfinaed,即 const
或 non-const
(这取决于 canChange
的值)。
正如您通过 运行 示例所见,结果是:
non const
const
即使 d1
和 d2
都被定义为非常量也是如此。
std::enable_if
在编译时打开正确的内部函数。
请注意,我使用了 C++14 提供的功能(例如 std::enable_if_t
)。
该示例可以很容易地转换为基于(std::enable_if_t
无非是 typename std::enable_if<condition, type>::type
等等)的 C++11。
下面是一个基于继承的例子:
#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
声明。它允许基 class 访问 Data
.
我想我会使用标准库中已有的模板来解决这个问题。它不需要继承或任何自定义 classes.
#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
}
现在回答问题:
Can I somehow avoid having two const/non-const functions here at compile time using some SFINAE magic?
你在这里几乎是在回答你自己的问题。 SFINAE 要求在直接上下文中考虑模板参数。这是表达 std::enable_if<>
中的表达式必须依赖于某种模板类型的复杂方式。
不幸的是,T 的模板类型在计算函数 GetDataRef
时已知,因此 enable_if 在这里帮不了我们。
因此,如果我们只想要 GetDataRef
的一个版本,我们确实必须求助于从模板类型派生(然后将在 T 的直接上下文中评估基础 class)。
但是,即使这样也有问题。
考虑:
Data<int, true>& x
这是对包含可变数据的可变容器的引用const Data<int, true>& y
这是对包含可变数据的不可变容器的引用
调用 x.GetDataRef()
应该 return 对 int 的可变引用,否则我们会让用户感到困惑。
调用 y.GetDataRef()
肯定 return 对 int 的 const 引用,否则,用户可能会再次震惊地发现 const 的成员实际上是可变的。
如果您可以使用 C++17,请查看 is_const
、add_const
和 remove_const
。
与 if constexpr ()
一起,一个相当优雅的解决方案应该是可能的。