【问题标题】:Avoid literally duplicating code for const and non-const with auto keyword?避免使用 auto 关键字从字面上复制 const 和 non-const 的代码?
【发布时间】:2018-08-29 15:15:23
【问题描述】:

好的,我做了一些研究,显然有很多关于这个主题的重复问题,仅举几例:

等等。但我忍不住再次提出这个问题,因为

  1. 使用 auto 类型的返回值,我实际上是在复制函数体,唯一的区别是 const 函数限定符。
  2. const 版本和非const 版本可能会返回彼此完全不兼容的类型。在这种情况下,Scott Meyers 的 const_cast 习语和“返回非 const 的私有 const 函数”技术都不起作用。

举个例子:

struct A {
    std::vector<int> examples;
    auto get() { return examples.begin(); }
    auto get() const { return examples.begin(); }
};  // Do I really have to duplicate?
    // My real-world code is much longer
    // and there are a lot of such get()'s

在这种特殊情况下,auto get() 返回一个iteratorauto get() const 返回一个const_iterator,并且两者不能相互转换(我知道erase(it,it) 技巧在这种情况下会进行转换,但那是不是重点)。 const_cast 因此不起作用;即使成功了,也需要程序员手动推导出auto的类型,完全违背了使用auto的目的。

那么除了宏真的没有别的办法了吗?

【问题讨论】:

  • How do I remove code duplication between similar const and non-const member functions? 的可能重复项我不明白在其他问题中如何回答。
  • @Drise 在这种情况下,该技术似乎并不适用,因为::const_iterator != const ::iterator
  • Meyer 的方法显然不适用于在const 和非const 版本中返回不同类型的成员函数,但我想说这是一个相当罕见的情况,在迭代器之外(虽然我可能是错的)。不确定是否有可能制定出一个通用的解决方案,但也许你可以拥有convert const_iterator to iterator 的函数(比如unconst_iterator)并拥有像auto get() { return unconst_iterator(example, static_cast&lt;const A &amp;&gt;(*this).get()); } 这样的模式。
  • @jdehesa 很公平。在我的情况下,它不是iterator,而是非常接近的东西(两个类似的类,一个有const 成员)。我确实可以在两者之间编写类型转换。感谢您的建议,但仍希望等待更优雅的答案。
  • P0847希望能解决这个问题

标签: c++14 c++ c++14


【解决方案1】:

好的,经过一番修改,我想出了以下两个解决方案,它们允许您保留自动返回类型并且只实现一次 getter。它使用与 Meyer 的相反的演员阵容。

C++ 11/14

这个版本只返回实现函数中的两个版本,或者使用cbegin(),或者如果你的类型没有它,这应该可以替代cbegin()return static_cast&lt;const A&amp;&gt;(*this).examples.begin();基本上转换为常量并使用正常的begin()函数来获取常数。

// Return both, and grab the required one
struct A
{
private:
    // This function does the actual getter work, hiding the template details
    // from the public interface, and allowing the use of auto as a return type
    auto get_do_work()
    {
        // Your getter logic etc.
        // ...
        // ...

        // Return both versions, but have the other functions extract the required one
        return std::make_pair(examples.begin(), examples.cbegin());
    }

public:
    std::vector<int> examples{ 0, 1, 2, 3, 4, 5 };

    // You'll get a regular iterator from the .first
    auto get()
    {
        return get_do_work().first;
    }

    // This will get a const iterator
    auto get() const
    {
        // Force using the non-const to get a const version here
        // Basically the inverse of Meyer's casting. Then just get
        // the const version of the type and return it
        return const_cast<A&>(*this).get_do_work().second;
    }

};

C++ 17 - 替代 if constexpr

这个应该会更好,因为它只返回一个值,并且在编译时就知道获得了哪个值,所以auto会知道该怎么做。否则,get() 函数的工作原理基本相同。

// With if constexpr
struct A
{
private:
    // This function does the actual getter work, hiding the template details
    // from the public interface, and allowing the use of auto as a return type
    template<bool asConst>
    auto get_do_work()
    {
        // Your getter logic etc.
        // ...
        // ...

        if constexpr (asConst)
        {
            return examples.cbegin();

            // Alternatively
            // return static_cast<const A&>(*this).examples.begin();
        }
        else
        {
            return examples.begin();
        }
    }

public:
    std::vector<int> examples{ 0, 1, 2, 3, 4, 5 };

    // Nothing special here, you'll get a regular iterator
    auto get()
    {
        return get_do_work<false>();
    }

    // This will get a const iterator
    auto get() const
    {
        // Force using the non-const to get a const version here
        // Basically the inverse of Meyer's casting, except you get a
        // const_iterator as a result, so no logical difference to users
        return const_cast<A&>(*this).get_do_work<true>();
    }
};

这可能适用于您的自定义类型,也可能不适用,但它适用于我,它解决了代码重复的需要,尽管它使用了一个辅助函数。但反过来实际的吸气剂变成单线,所以这应该是合理的。

以下主要功能用于测试两种解决方案,并按预期工作:

int main()
{    
    const A a;
    *a.get() += 1; // This is an error since it returns const_iterator

    A b;
    *b.get() += 1; // This works fine

    std::cout << *a.get() << "\n";
    std::cout << *b.get() << "\n";

    return 0;
}

【讨论】:

  • 现在每个 getter 需要 3 个方法而不是 2 个。它并没有变得更简单......
  • 但是你不要复制函数体,保持实现的一致性。正如问题中所问的那样,在一个地方改变它并不意味着你必须改变另一个地方。还按照要求避免使用宏。
  • 赞成。我认为这是一个非常公平的解决方案 :) 让我拭目以待,看看是否还有更好的解决方案
  • 它原则上应该可以工作,尽管如果get_do_work 修改了对象的状态,它会在没有警告的情况下向 UB 敞开大门。我什至无法确定在const_cast-ed vector 上调用(非const.begin() 是否保证安全。
  • 已接受。我想指出@jonathan-wakely 发表的评论。现有提案P0847 似乎确实针对我所要求的确切情况,所以我认为在当前标准中不存在优雅的解决方案。
【解决方案2】:
struct A {
    std::vector<int> examples;

private:
    template <typename ThisType>
    static auto
    get_tmpl(ThisType& t) { return t.examples.begin(); }

public:
    auto get()       { return get_tmpl(*this); }
    auto get() const { return get_tmpl(*this); }
};

上面的呢?是的,您仍然需要声明这两种方法,但逻辑可以包含在单个静态模板方法中。通过为返回类型添加模板参数,这甚至适用于 C++11 之前的代码。

【讨论】:

    【解决方案3】:

    只是一个假设的解决方案,一旦我们有了概念,我正在考虑将其应用到每个地方:使用免费朋友函数代替成员函数

    //Forwarding ref concept
    template<class T,class U>
    concept FRef = std::is_same_v<std::decay_t<T>,std::decay_t<U>>;
    
    struct A{
      std::vector<int> examples;
    
      friend decltype(auto) get(FRef{T,A}&& aA){
        return std::forward<T>(aA).examples.begin();
          //the std::forward is actualy not necessary here 
          //because there are no overload value reference overload of begin.
        }
      };
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-08-09
      • 2021-09-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-01-10
      相关资源
      最近更新 更多