如何更喜欢调用 const 成员函数并回退到非 const 版本?
How to prefer calling const member function and fallback to non-const version?
考虑一个 class Bar
的两个版本的库:
/// v1
class Bar
{
void get_drink()
{
std::cout << "non-const get_drink() called" << std::endl;
}
};
/// v2
class Bar
{
void get_drink()
{
std::cout << "non-const get_drink() called" << std::endl;
}
void get_drink() const
{
std::cout << "const get_drink() called" << std::endl;
}
};
在客户端代码中,我们拥有一个 Bar
对象并想要 get_drink
。如果可用(使用 v2 库时),我希望能够更喜欢调用 get_drink()
的 const 版本,并回退到非 const 版本(使用 v1 库时)。即:
Bar bar;
bar.get_drink(); // this does not call the const version of v2
static_cast<const Bar&>(bar).get_drink(); // this does not compile against v1 library
遗憾的是,该库没有版本控制,没有其他方法可以区分这两个版本。
我认为必须使用一些模板魔法。那么问题是如何?
这似乎有效:
#include <type_traits>
#include <iostream>
template<typename T, typename=void>
struct find_a_drink {
void operator()(T &t) const
{
t.get_drink();
}
};
template<typename T>
struct find_a_drink<T,
std::void_t<decltype( std::declval<const T &>()
.get_drink())>
> {
void operator()(T &t) const
{
static_cast<const T &>(t).get_drink();
}
};
template<typename T>
void drink_from(T &&t)
{
find_a_drink<std::remove_reference_t<T>>{}(t);
}
/// v1
class Bar1
{
public:
void get_drink()
{
std::cout << "non-const get_drink() called (Bar1)" << std::endl;
}
};
class Bar2
{
public:
void get_drink()
{
std::cout << "non-const get_drink() called (Bar2)" << std::endl;
}
void get_drink() const
{
std::cout << "const get_drink() called (Bar2)" << std::endl;
}
};
int main()
{
Bar1 b1;
Bar2 b2;
drink_from(b1);
drink_from(b2);
return 0;
}
结果:
non-const get_drink() called (Bar1)
const get_drink() called (Bar2)
这与 的基本思想相同,但使用表达式 sfinae,我认为这更清楚
// 1
template<typename T>
auto drink_from(T &t) -> decltype(static_cast<const T&>(t).get_drink())
{
static_cast<const T &>(t).get_drink();
}
// 2
template<typename T>
auto drink_from(T &&t)
{
t.get_drink();
}
如果参数 t
可以转换为 const
并调用 get_drink
,则首选 Overload 1,这需要存在 const 限定成员。如果这样的成员不可用,则调用重载 2,调用非 const 限定成员。
Bar b;
drink_from(b); // calls const qualified member if available
考虑一个 class Bar
的两个版本的库:
/// v1
class Bar
{
void get_drink()
{
std::cout << "non-const get_drink() called" << std::endl;
}
};
/// v2
class Bar
{
void get_drink()
{
std::cout << "non-const get_drink() called" << std::endl;
}
void get_drink() const
{
std::cout << "const get_drink() called" << std::endl;
}
};
在客户端代码中,我们拥有一个 Bar
对象并想要 get_drink
。如果可用(使用 v2 库时),我希望能够更喜欢调用 get_drink()
的 const 版本,并回退到非 const 版本(使用 v1 库时)。即:
Bar bar;
bar.get_drink(); // this does not call the const version of v2
static_cast<const Bar&>(bar).get_drink(); // this does not compile against v1 library
遗憾的是,该库没有版本控制,没有其他方法可以区分这两个版本。
我认为必须使用一些模板魔法。那么问题是如何?
这似乎有效:
#include <type_traits>
#include <iostream>
template<typename T, typename=void>
struct find_a_drink {
void operator()(T &t) const
{
t.get_drink();
}
};
template<typename T>
struct find_a_drink<T,
std::void_t<decltype( std::declval<const T &>()
.get_drink())>
> {
void operator()(T &t) const
{
static_cast<const T &>(t).get_drink();
}
};
template<typename T>
void drink_from(T &&t)
{
find_a_drink<std::remove_reference_t<T>>{}(t);
}
/// v1
class Bar1
{
public:
void get_drink()
{
std::cout << "non-const get_drink() called (Bar1)" << std::endl;
}
};
class Bar2
{
public:
void get_drink()
{
std::cout << "non-const get_drink() called (Bar2)" << std::endl;
}
void get_drink() const
{
std::cout << "const get_drink() called (Bar2)" << std::endl;
}
};
int main()
{
Bar1 b1;
Bar2 b2;
drink_from(b1);
drink_from(b2);
return 0;
}
结果:
non-const get_drink() called (Bar1)
const get_drink() called (Bar2)
这与
// 1
template<typename T>
auto drink_from(T &t) -> decltype(static_cast<const T&>(t).get_drink())
{
static_cast<const T &>(t).get_drink();
}
// 2
template<typename T>
auto drink_from(T &&t)
{
t.get_drink();
}
如果参数 t
可以转换为 const
并调用 get_drink
,则首选 Overload 1,这需要存在 const 限定成员。如果这样的成员不可用,则调用重载 2,调用非 const 限定成员。
Bar b;
drink_from(b); // calls const qualified member if available