287

假设我有以下class X要返回对内部成员的访问权限的地方:

class Z
{
    // details
};

class X
{
    std::vector<Z> vecZ;

public:
    Z& Z(size_t index)
    {
        // massive amounts of code for validating index

        Z& ret = vecZ[index];

        // even more code for determining that the Z instance
        // at index is *exactly* the right sort of Z (a process
        // which involves calculating leap years in which
        // religious holidays fall on Tuesdays for
        // the next thousand years or so)

        return ret;
    }
    const Z& Z(size_t index) const
    {
        // identical to non-const X::Z(), except printed in
        // a lighter shade of gray since
        // we're running low on toner by this point
    }
};

这两个成员函数在大括号X::Z()X::Z() const具有相同的代码。这是重复的代码,可能会导致逻辑复杂的长函数出现维护问题

有没有办法避免这种代码重复?

4

21 回答 21

221

有关详细说明,请参阅第 10 页的标题“避免重复const和非const成员函数”。23,在第 3 项“const尽可能使用”中,在Effective C++中,由 Scott Meyers 编辑的第 3 版,ISBN-13:9780321334879

替代文字

这是迈耶斯的解决方案(简化):

struct C {
  const char & get() const {
    return c;
  }
  char & get() {
    return const_cast<char &>(static_cast<const C &>(*this).get());
  }
  char c;
};

这两个强制转换和函数调用可能很难看,但在非const方法中是正确的,因为这意味着该对象不是const开始的。(迈耶斯对此进行了彻底的讨论。)

于 2008-09-23T21:24:13.750 回答
78

C++17 更新了这个问题的最佳答案:

T const & f() const {
    return something_complicated();
}
T & f() {
    return const_cast<T &>(std::as_const(*this).f());
}

这样做的好处是:

  • 很明显发生了什么
  • 具有最小的代码开销——它适合一行
  • 很难出错(只能volatile被意外抛弃,但是volatile是罕见的限定词)

如果你想走完整的扣除路线,那么可以通过一个辅助函数来完成

template<typename T>
constexpr T & as_mutable(T const & value) noexcept {
    return const_cast<T &>(value);
}
template<typename T>
constexpr T * as_mutable(T const * value) noexcept {
    return const_cast<T *>(value);
}
template<typename T>
constexpr T * as_mutable(T * value) noexcept {
    return value;
}
template<typename T>
void as_mutable(T const &&) = delete;

现在你甚至不能搞砸了volatile,用法看起来像

decltype(auto) f() const {
    return something_complicated();
}
decltype(auto) f() {
    return as_mutable(std::as_const(*this).f());
}
于 2017-11-18T17:51:58.977 回答
72

是的,可以避免代码重复。您需要使用 const 成员函数来拥有逻辑并让非常量成员函数调用 const 成员函数并将返回值重新转换为非常量引用(如果函数返回指针,则为指针):

class X
{
   std::vector<Z> vecZ;

public:
   const Z& z(size_t index) const
   {
      // same really-really-really long access 
      // and checking code as in OP
      // ...
      return vecZ[index];
   }

   Z& z(size_t index)
   {
      // One line. One ugly, ugly line - but just one line!
      return const_cast<Z&>( static_cast<const X&>(*this).z(index) );
   }

 #if 0 // A slightly less-ugly version
   Z& Z(size_t index)
   {
      // Two lines -- one cast. This is slightly less ugly but takes an extra line.
      const X& constMe = *this;
      return const_cast<Z&>( constMe.z(index) );
   }
 #endif
};

注意:重要的是不要将逻辑放在非常量函数中并且让常量函数调用非常量函数 - 这可能会导致未定义的行为。原因是常量类实例被强制转换为非常量实例。非常量成员函数可能会意外修改类,C++ 标准规定这将导致未定义的行为。

于 2008-09-23T20:48:04.197 回答
37

我认为 Scott Meyers 的解决方案可以通过使用 tempate 辅助函数在 C++11 中得到改进。这使得意图更加明显,并且可以被许多其他 getter 重用。

template <typename T>
struct NonConst {typedef T type;};
template <typename T>
struct NonConst<T const> {typedef T type;}; //by value
template <typename T>
struct NonConst<T const&> {typedef T& type;}; //by reference
template <typename T>
struct NonConst<T const*> {typedef T* type;}; //by pointer
template <typename T>
struct NonConst<T const&&> {typedef T&& type;}; //by rvalue-reference

template<typename TConstReturn, class TObj, typename... TArgs>
typename NonConst<TConstReturn>::type likeConstVersion(
   TObj const* obj,
   TConstReturn (TObj::* memFun)(TArgs...) const,
   TArgs&&... args) {
      return const_cast<typename NonConst<TConstReturn>::type>(
         (obj->*memFun)(std::forward<TArgs>(args)...));
}

可以通过以下方式使用此辅助函数。

struct T {
   int arr[100];

   int const& getElement(size_t i) const{
      return arr[i];
   }

   int& getElement(size_t i) {
      return likeConstVersion(this, &T::getElement, i);
   }
};

第一个参数始终是 this 指针。第二个是指向要调用的成员函数的指针。之后,可以传递任意数量的附加参数,以便将它们转发给函数。由于可变参数模板,这需要 C++11。

于 2013-05-27T20:43:51.590 回答
30

很好的问题和很好的答案。我有另一个解决方案,它不使用强制转换:

class X {

private:

    std::vector<Z> v;

    template<typename InstanceType>
    static auto get(InstanceType& instance, std::size_t i) -> decltype(instance.get(i)) {
        // massive amounts of code for validating index
        // the instance variable has to be used to access class members
        return instance.v[i];
    }

public:

    const Z& get(std::size_t i) const {
        return get(*this, i);
    }

    Z& get(std::size_t i) {
        return get(*this, i);
    }

};

但是,它的丑陋之处在于需要一个静态成员,并且需要在其中使用instance变量。

我没有考虑此解决方案的所有可能(负面)影响。如果有请告诉我。

于 2013-12-08T11:16:32.523 回答
22

比迈耶斯更冗长,但我可能会这样做:

class X {

    private:

    // This method MUST NOT be called except from boilerplate accessors.
    Z &_getZ(size_t index) const {
        return something;
    }

    // boilerplate accessors
    public:
    Z &getZ(size_t index)             { return _getZ(index); }
    const Z &getZ(size_t index) const { return _getZ(index); }
};

私有方法有一个不受欢迎的属性,它为一个 const 实例返回一个非常量 Z&,这就是它是私有的原因。私有方法可能会破坏外部接口的不变量(在这种情况下,所需的不变量是“不能通过通过它获得的对它拥有的对象的引用来修改 const 对象”)。

请注意,注释是模式的一部分 - _getZ 的接口指定调用它永远无效(显然,除了访问器):无论如何,这样做没有任何可以想象的好处,因为它需要多输入 1 个字符并且不会导致更小或更快的代码。调用该方法等同于使用 const_cast 调用其中一个访问器,您也不想这样做。如果您担心使错误变得明显(这是一个公平的目标),则将其称为 const_cast_getZ 而不是 _getZ。

顺便说一句,我很欣赏迈耶斯的解决方案。我对此没有哲学上的反对意见。不过,就个人而言,我更喜欢一点点受控的重复,以及只能在某些严格控制的情况下调用的私有方法,而不是看起来像线路噪音的方法。选择你的毒药并坚持下去。

[编辑:Kevin 正确地指出 _getZ 可能想要调用另一个方法(比如 generateZ),它与 getZ 一样是 const-specialized。在这种情况下,_getZ 会看到一个 const Z& 并且必须在返回之前对其进行 const_cast。这仍然是安全的,因为样板访问器会监控所有内容,但它的安全性并不是很明显。此外,如果您这样做,然后将 generateZ 更改为始终返回 const,那么您还需要将 getZ 更改为始终返回 const,但编译器不会告诉您这样做。

关于编译器的后一点也适用于 Meyers 推荐的模式,但关于非显而易见的 const_cast 的第一点不是。所以总的来说,我认为如果 _getZ 需要一个 const_cast 作为它的返回值,那么这种模式比 Meyers 的模式失去了很多价值。由于与迈耶斯相比,它也有劣势,我想在那种情况下我会改用他的。从一个重构到另一个很容易——它不会影响类中的任何其他有效代码,因为只有无效代码和样板调用_getZ。]

于 2008-09-23T22:02:52.287 回答
8

您也可以使用模板解决此问题。这个解决方案有点难看(但丑陋隐藏在 .cpp 文件中),但它确实提供了对 constness 的编译器检查,并且没有代码重复。

.h 文件:

#include <vector>

class Z
{
    // details
};

class X
{
    std::vector<Z> vecZ;

public:
    const std::vector<Z>& GetVector() const { return vecZ; }
    std::vector<Z>& GetVector() { return vecZ; }

    Z& GetZ( size_t index );
    const Z& GetZ( size_t index ) const;
};

.cpp 文件:

#include "constnonconst.h"

template< class ParentPtr, class Child >
Child& GetZImpl( ParentPtr parent, size_t index )
{
    // ... massive amounts of code ...

    // Note you may only use methods of X here that are
    // available in both const and non-const varieties.

    Child& ret = parent->GetVector()[index];

    // ... even more code ...

    return ret;
}

Z& X::GetZ( size_t index )
{
    return GetZImpl< X*, Z >( this, index );
}

const Z& X::GetZ( size_t index ) const
{
    return GetZImpl< const X*, const Z >( this, index );
}

我可以看到的主要缺点是,因为该方法的所有复杂实现都在一个全局函数中,你要么需要使用上面的 GetVector() 等公共方法来获取 X 的成员(其中总是需要一个const 和非 const 版本),或者您可以将此函数设为朋友。但我不喜欢朋友。

[编辑:删除了测试期间添加的 cstdio 的不需要的包含。]

于 2009-01-13T16:21:19.777 回答
6

对于那些(像我一样)谁

  • 使用c++17
  • 想要添加最少的样板/重复和
  • 不介意使用(在等待元类时......),

这是另一种看法:

#include <utility>
#include <type_traits>

template <typename T> struct NonConst;
template <typename T> struct NonConst<T const&> {using type = T&;};
template <typename T> struct NonConst<T const*> {using type = T*;};

#define NON_CONST(func)                                                     \
    template <typename... T> auto func(T&&... a)                            \
        -> typename NonConst<decltype(func(std::forward<T>(a)...))>::type   \
    {                                                                       \
        return const_cast<decltype(func(std::forward<T>(a)...))>(           \
            std::as_const(*this).func(std::forward<T>(a)...));              \
    }

它基本上是来自@Pait、@DavidStone 和@sh1 的答案的混合(编辑:和@cdhowie 的改进)。它添加到表中的是,您只需多写一行代码即可轻松命名函数(但没有参数或返回类型重复):

class X
{
    const Z& get(size_t index) const { ... }
    NON_CONST(get)
};

注意:gcc 在 8.1 之前无法编译,clang-5 及更高版本以及 MSVC-19 都很高兴(根据编译器资源管理器)。

于 2019-03-29T22:19:11.590 回答
5

如果您不喜欢const强制转换,我使用另一个答案建议的模板静态辅助函数的 C++17 版本,以及可选的 SFINAE 测试。

#include <type_traits>

#define REQUIRES(...)         class = std::enable_if_t<(__VA_ARGS__)>
#define REQUIRES_CV_OF(A,B)   REQUIRES( std::is_same_v< std::remove_cv_t< A >, B > )

class Foobar {
private:
    int something;

    template<class FOOBAR, REQUIRES_CV_OF(FOOBAR, Foobar)>
    static auto& _getSomething(FOOBAR& self, int index) {
        // big, non-trivial chunk of code...
        return self.something;
    }

public:
    auto& getSomething(int index)       { return _getSomething(*this, index); }
    auto& getSomething(int index) const { return _getSomething(*this, index); }
};

完整版:https ://godbolt.org/z/mMK4r3

于 2019-12-05T18:23:53.257 回答
4

虽然这里的大多数答案都建议使用 a const_cast,但 CppCoreGuidelines 有一个关于此的部分

相反,更喜欢共享实现。通常,您可以让非常量函数调用 const 函数。但是,当存在复杂的逻辑时,这可能会导致以下仍然使用 const_cast 的模式:

class Foo {
public:
    // not great, non-const calls const version but resorts to const_cast
    Bar& get_bar()
    {
        return const_cast<Bar&>(static_cast<const Foo&>(*this).get_bar());
    }
    const Bar& get_bar() const
    {
        /* the complex logic around getting a const reference to my_bar */
    }
private:
    Bar my_bar;
};

尽管此模式在正确应用时是安全的,因为调用者必须以非常量对象开头,但它并不理想,因为安全性很难作为检查器规则自动执行。

相反,更喜欢将通用代码放在通用辅助函数中——并将其作为模板,以便推导出 const。这根本不使用任何 const_cast :

class Foo {
public:                         // good
          Bar& get_bar()       { return get_bar_impl(*this); }
    const Bar& get_bar() const { return get_bar_impl(*this); }
private:
    Bar my_bar;

    template<class T>           // good, deduces whether T is const or non-const
    static auto& get_bar_impl(T& t)
        { /* the complex logic around getting a possibly-const reference to my_bar */ }
};

注意:不要在模板内做大的非依赖工作,这会导致代码膨胀。例如,如果 get_bar_impl 的全部或部分可以是非依赖的并分解为一个通用的非模板函数,则进一步的改进可能会大大减少代码大小。

于 2021-06-23T12:42:15.923 回答
3

将逻辑移动到私有方法中,并且只在 getter 中执行“获取引用并返回”内容怎么样?实际上,我会对简单 getter 函数中的静态和 const 强制转换感到相当困惑,除了极少数情况外,我认为这很丑陋!

于 2008-09-23T22:05:02.873 回答
2

使用预处理器是作弊吗?

struct A {

    #define GETTER_CORE_CODE       \
    /* line 1 of getter code */    \
    /* line 2 of getter code */    \
    /* .....etc............. */    \
    /* line n of getter code */       

    // ^ NOTE: line continuation char '\' on all lines but the last

   B& get() {
        GETTER_CORE_CODE
   }

   const B& get() const {
        GETTER_CORE_CODE
   }

   #undef GETTER_CORE_CODE

};

它不像模板或演员表那样花哨,但它确实使您的意图(“这两个函数要相同”)非常明确。

于 2017-01-14T02:33:05.173 回答
2

令我惊讶的是,有这么多不同的答案,但几乎都依赖于沉重的模板魔法。模板很强大,但有时宏在简洁性方面胜过它们。通过将两者结合起来,通常可以实现最大的多功能性。

我写了一个宏FROM_CONST_OVERLOAD(),它可以放在非常量函数中来调用 const 函数。

示例用法:

class MyClass
{
private:
    std::vector<std::string> data = {"str", "x"};

public:
    // Works for references
    const std::string& GetRef(std::size_t index) const
    {
        return data[index];
    }

    std::string& GetRef(std::size_t index)
    {
        return FROM_CONST_OVERLOAD( GetRef(index) );
    }


    // Works for pointers
    const std::string* GetPtr(std::size_t index) const
    {
        return &data[index];
    }

    std::string* GetPtr(std::size_t index)
    {
        return FROM_CONST_OVERLOAD( GetPtr(index) );
    }
};

简单且可重用的实现:

template <typename T>
T& WithoutConst(const T& ref)
{
    return const_cast<T&>(ref);
}

template <typename T>
T* WithoutConst(const T* ptr)
{
    return const_cast<T*>(ptr);
}

template <typename T>
const T* WithConst(T* ptr)
{
    return ptr;
}

#define FROM_CONST_OVERLOAD(FunctionCall) \
  WithoutConst(WithConst(this)->FunctionCall)

解释:

正如许多答案中所发布的,在非常量成员函数中避免代码重复的典型模式是:

return const_cast<Result&>( static_cast<const MyClass*>(this)->Method(args) );

使用类型推断可以避免很多这种样板。首先,const_cast可以封装在 中WithoutConst(),它推断其参数的类型并删除 const 限定符。其次,可以使用类似的方法来WithConst()对指针进行 const 限定this,从而可以调用 const 重载方法。

其余的是一个简单的宏,它在调用前加上正确限定的前缀,this->并从结果中删除 const。由于宏中使用的表达式几乎总是带有 1:1 转发参数的简单函数调用,因此不会出现诸如多重评估之类的宏的缺点。省略号和__VA_ARGS__也可以使用,但不应该需要,因为逗号(如参数分隔符)出现在括号内。

这种方法有几个好处:

  • 最小且自然的语法——只需将调用包装在FROM_CONST_OVERLOAD( )
  • 不需要额外的成员函数
  • 兼容 C++98
  • 简单的实现,没有模板元编程和零依赖
  • 可扩展:可以添加其他 const 关系(如const_iteratorstd::shared_ptr<const T>等)。为此,只需WithoutConst()对相应类型进行重载即可。

限制:此解决方案针对非常量重载与 const 重载完全相同的场景进行了优化,因此可以 1:1 转发参数。如果您的逻辑不同并且您没有通过调用 const 版本this->Method(args),则可以考虑其他方法。

于 2019-06-20T22:09:31.970 回答
2

由于推断出,C++23 更新了这个问题的最佳答案:

struct s {
    auto && f(this auto && self) {
        // all the common code goes here
    }
};

单个函数模板可作为普通成员函数调用,并为您推导出正确的引用类型。没有错误的转换,没有为概念上一件事的东西编写多个函数。

于 2021-10-07T18:44:17.557 回答
1

我建议使用私有助手静态函数模板,如下所示:

class X
{
    std::vector<Z> vecZ;

    // ReturnType is explicitly 'Z&' or 'const Z&'
    // ThisType is deduced to be 'X' or 'const X'
    template <typename ReturnType, typename ThisType>
    static ReturnType Z_impl(ThisType& self, size_t index)
    {
        // massive amounts of code for validating index
        ReturnType ret = self.vecZ[index];
        // even more code for determining, blah, blah...
        return ret;
    }

public:
    Z& Z(size_t index)
    {
        return Z_impl<Z&>(*this, index);
    }
    const Z& Z(size_t index) const
    {
        return Z_impl<const Z&>(*this, index);
    }
};
于 2015-10-07T14:25:20.647 回答
1

我想出了一个宏,它可以自动生成成对的 const/non-const 函数。

class A
{
    int x;    
  public:
    MAYBE_CONST(
        CV int &GetX() CV {return x;}
        CV int &GetY() CV {return y;}
    )

    //   Equivalent to:
    // int &GetX() {return x;}
    // int &GetY() {return y;}
    // const int &GetX() const {return x;}
    // const int &GetY() const {return y;}
};

请参阅答案的结尾以了解实施。

的参数MAYBE_CONST是重复的。在第一个副本中,CV被替换为空;在第二个副本中,它被替换为const.

CV宏参数中可以出现多少次没有限制。

不过有一点不便。如果CV出现在括号内,则这对括号必须以 为前缀CV_IN

// Doesn't work
MAYBE_CONST( CV int &foo(CV int &); )

// Works, expands to
//         int &foo(      int &);
//   const int &foo(const int &);
MAYBE_CONST( CV int &foo CV_IN(CV int &); )

执行:

#define MAYBE_CONST(...) IMPL_CV_maybe_const( (IMPL_CV_null,__VA_ARGS__)() )
#define CV )(IMPL_CV_identity,
#define CV_IN(...) )(IMPL_CV_p_open,)(IMPL_CV_null,__VA_ARGS__)(IMPL_CV_p_close,)(IMPL_CV_null,

#define IMPL_CV_null(...)
#define IMPL_CV_identity(...) __VA_ARGS__
#define IMPL_CV_p_open(...) (
#define IMPL_CV_p_close(...) )

#define IMPL_CV_maybe_const(seq) IMPL_CV_a seq IMPL_CV_const_a seq

#define IMPL_CV_body(cv, m, ...) m(cv) __VA_ARGS__

#define IMPL_CV_a(...) __VA_OPT__(IMPL_CV_body(,__VA_ARGS__) IMPL_CV_b)
#define IMPL_CV_b(...) __VA_OPT__(IMPL_CV_body(,__VA_ARGS__) IMPL_CV_a)

#define IMPL_CV_const_a(...) __VA_OPT__(IMPL_CV_body(const,__VA_ARGS__) IMPL_CV_const_b)
#define IMPL_CV_const_b(...) __VA_OPT__(IMPL_CV_body(const,__VA_ARGS__) IMPL_CV_const_a)

不支持的 Pre-C++20 实现CV_IN

#define MAYBE_CONST(...) IMPL_MC( ((__VA_ARGS__)) )
#define CV ))((

#define IMPL_MC(seq) \
    IMPL_MC_end(IMPL_MC_a seq) \
    IMPL_MC_end(IMPL_MC_const_0 seq)

#define IMPL_MC_identity(...) __VA_ARGS__
#define IMPL_MC_end(...) IMPL_MC_end_(__VA_ARGS__)
#define IMPL_MC_end_(...) __VA_ARGS__##_end

#define IMPL_MC_a(elem) IMPL_MC_identity elem IMPL_MC_b
#define IMPL_MC_b(elem) IMPL_MC_identity elem IMPL_MC_a
#define IMPL_MC_a_end
#define IMPL_MC_b_end

#define IMPL_MC_const_0(elem)       IMPL_MC_identity elem IMPL_MC_const_a
#define IMPL_MC_const_a(elem) const IMPL_MC_identity elem IMPL_MC_const_b
#define IMPL_MC_const_b(elem) const IMPL_MC_identity elem IMPL_MC_const_a
#define IMPL_MC_const_a_end
#define IMPL_MC_const_b_end
于 2019-10-19T17:43:08.630 回答
0

通常,需要 const 和非 const 版本的成员函数是 getter 和 setter。大多数时候它们是单行的,所以代码重复不是问题。

于 2008-09-23T21:11:44.763 回答
0

我这样做是为了一个有正当理由使用 ... 不知道的朋友做的const_cast,我可能会做这样的事情(不是很优雅):

#include <iostream>

class MyClass
{

public:

    int getI()
    {
        std::cout << "non-const getter" << std::endl;
        return privateGetI<MyClass, int>(*this);
    }

    const int getI() const
    {
        std::cout << "const getter" << std::endl;
        return privateGetI<const MyClass, const int>(*this);
    }

private:

    template <class C, typename T>
    static T privateGetI(C c)
    {
        //do my stuff
        return c._i;
    }

    int _i;
};

int main()
{
    const MyClass myConstClass = MyClass();
    myConstClass.getI();

    MyClass myNonConstClass;
    myNonConstClass.getI();

    return 0;
}
于 2015-06-16T13:13:06.670 回答
-1

这篇 DDJ 文章展示了一种使用模板专业化的方法,该方法不需要您使用 const_cast。对于这样一个简单的功能,它确实是不需要的。

boost::any_cast (在某一时刻,它不再)使用来自 const 版本的 const_cast 调用非常量版本以避免重复。你不能在非常量版本上强加 const 语义,所以你必须非常小心。

最后,只要两个片段直接在彼此之上,一些代码重复是可以的。

于 2008-09-23T20:53:47.723 回答
-1

要添加到 jwfearn 和 kevin 提供的解决方案,以下是函数返回 shared_ptr 时的相应解决方案:

struct C {
  shared_ptr<const char> get() const {
    return c;
  }
  shared_ptr<char> get() {
    return const_pointer_cast<char>(static_cast<const C &>(*this).get());
  }
  shared_ptr<char> c;
};
于 2014-11-20T14:06:10.343 回答
-1

没有找到我要找的东西,所以我自己滚动了几个......

这个有点罗嗦,但它的优点是可以同时处理许多同名(和返回类型)的重载方法:

struct C {
  int x[10];

  int const* getp() const { return x; }
  int const* getp(int i) const { return &x[i]; }
  int const* getp(int* p) const { return &x[*p]; }

  int const& getr() const { return x[0]; }
  int const& getr(int i) const { return x[i]; }
  int const& getr(int* p) const { return x[*p]; }

  template<typename... Ts>
  auto* getp(Ts... args) {
    auto const* p = this;
    return const_cast<int*>(p->getp(args...));
  }

  template<typename... Ts>
  auto& getr(Ts... args) {
    auto const* p = this;
    return const_cast<int&>(p->getr(args...));
  }
};

如果const每个名称只有一个方法,但仍有很多方法要复制,那么您可能更喜欢这个:

  template<typename T, typename... Ts>
  auto* pwrap(T const* (C::*f)(Ts...) const, Ts... args) {
    return const_cast<T*>((this->*f)(args...));
  }

  int* getp_i(int i) { return pwrap(&C::getp_i, i); }
  int* getp_p(int* p) { return pwrap(&C::getp_p, p); }

不幸的是,一旦您开始重载名称,这就会崩溃(此时函数指针参数的参数列表似乎未解析,因此无法找到函数参数的匹配项)。尽管您也可以使用模板来摆脱这种情况:

  template<typename... Ts>
  auto* getp(Ts... args) { return pwrap<int, Ts...>(&C::getp, args...); }

但是该方法的引用参数const无法与模板的明显按值参数匹配,并且它会中断。 不知道为什么。这就是为什么

于 2016-12-15T04:15:17.383 回答