如何更喜欢调用const成员函数并回退到非const版本?

7
考虑一个库中两个版本的类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()方法有const版本和非const版本,我希望在使用v2库时优先调用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

很不幸,该库没有版本号,也没有其他方法来区分两个版本。

我认为需要使用一些模板魔法。问题是如何实现呢?


我猜把被标记为const的方法重命名为get_drink_const()会被认为是作弊吗? :) - Jeremy Friesner
为什么在v2中不从非const方法调用const方法?例如,在比较运算符中广泛使用此方法。 - 273K
为什么不直接用#if(n)def将两个版本包装起来,然后在任何给定的构建中针对你正在编译的版本进行#define呢? - Remy Lebeau
2个回答

2

这似乎有效:

#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)

2

这与Sam的回答的基本思路相同,但使用表达式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,则首选重载1,这需要存在一个带有const限定符成员。如果不存在此类成员,则调用重载2,该重载调用非const限定符成员。

Bar b;
drink_from(b);  // calls const qualified member if available

demo


但是 drink_from(Bar{}); 调用了错误的重载演示 - Jarod42
@Jarod42 哎呀,你说得对。让我看看能不能修复它。谢谢。 - cigien

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接