【问题标题】:detecting protected constructors of (possibly abstract) base class检测(可能是抽象的)基类的受保护构造函数
【发布时间】:2012-08-18 06:22:14
【问题描述】:

我正在试验 C++11 的新特性。在我的设置中,我真的很想使用继承构造函数,但不幸的是还没有编译器实现这些。因此,我试图模拟相同的行为。我可以这样写:

template <class T>
class Wrapper : public T {
    public:
    template <typename... As>
    Wrapper(As && ... as) : T { std::forward<As>(as)... } { }
    // ... nice additions to T ...
};

这很有效……大多数时候。有时使用Wrapper 类的代码必须使用SFINAE 来检测如何构造这样的Wrapper&lt;T&gt;。然而,存在以下问题:就重载决议而言,Wrapper&lt;T&gt; 的构造函数将接受任何参数——但如果类型为T 无法使用这些构造。

我试图使用enable_if 有条件地启用构造函数模板的不同实例化

    template <typename... As, typename std::enable_if<std::is_constructible<T, As && ...>::value, int>::type = 0>
    Wrapper(As && ... as) // ...

只要满足以下条件就可以正常工作:

  • T 的适当构造函数是public
  • T 不是抽象的

我的问题是:如何摆脱上述两个约束?

我试图通过检查(使用 SFINAE 和 sizeof())表达式 new T(std::declval&lt;As &amp;&amp;&gt;()...) 是否格式良好 within Wrapper&lt;T&gt; 来克服第一个问题。但这当然行不通,因为派生类可以使用其基类的受保护构造函数的唯一方法是在成员初始化列表中。

对于第二个,我完全不知道——它是我需要的更多,因为有时它是实现T 的抽象函数的Wrapper,使其成为一个完整的类型。

我想要一个解决方案:

  • 按照标准是正确的
  • 适用于任何 gcc-4.6.*、gcc-4.7.* 或 clang-3.*

谢谢!

【问题讨论】:

  • 我很着急,但也许 stackoverflow.com/questions/8984013/… 可以在这里提供帮助,但我不会指望 gcc 4.6 能做到这一点
  • 这里的访问控制有点棘手:如果您使用sizeof(),编译器将检查整个表达式,包括访问——但随后会从表达式的上下文中检查访问 ,在受保护的构造函数的情况下失败;除了sizeof 之外的所有东西都只在重载解析和类型推断级别起作用,所以访问冲突不会触发 SFINAE——但是,我看不出用构造函数做某事,因为它不能作为模板参数传递.至于编译器支持,如果上面的 any 接受代码,我会很高兴。

标签: c++ templates c++11 template-meta-programming perfect-forwarding


【解决方案1】:

这似乎在我的本地 GCC(4.7,由 rubenvb 提供)上运行良好。不过,ideone 上的 GCC 会打印几个“已实现”的编译器内部错误。

我不得不公开Experiment 类的“实现细节”,因为出于某些原因(闻起来像一个错误),我的 GCC 版本抱怨它们是私有的,即使只有类本身使用它.

#include <utility>

template<typename T, typename Ignored>
struct Ignore { typedef T type; };

struct EatAll {
  template<typename ...T>
  EatAll(T&&...) {}
};

template<typename T>
struct Experiment : T {
public:
  typedef char yes[1];
  typedef char no[2];

  static void check1(T const&);
  static void check1(EatAll);

  // if this SFINAE fails, T accepts it
  template<typename ...U>
  static auto check(int, U&&...u)
    -> typename Ignore<no&, 
        decltype(Experiment::check1({std::forward<U>(u)...}))>::type;

  template<typename ...U>
  static yes &check(long, U&&...);

public:
  void f() {}
  template<typename ...U, 
           typename std::enable_if<
             std::is_same<decltype(Experiment::check(0, std::declval<U>()...)),
                          yes&>::value, int>::type = 0>
  Experiment(U &&...u):T{ std::forward<U>(u)... }
  {}
};

// TEST

struct AbstractBase {
  protected:
    AbstractBase(int, float);
    virtual void f() = 0;
};

struct Annoyer { Annoyer(int); };

void x(Experiment<AbstractBase>);
void x(Annoyer);

int main() {
  x({42});
  x({42, 43.f});
}

更新:该代码也适用于 Clang。

【讨论】:

  • 我必须承认,非常聪明地使用了歧义。让我检查一下它是否适用于我的设置。顺便问一下,它是如何处理私有基础构造函数的?
  • @GrzegorzHerman 私有基础构造函数未被检查。我必须承认:(所以它会认为转换是可能的。
  • 我就是这么想的——但这对我来说是个小问题,最重要的是让它适用于抽象类:)
  • 但是,如果您尝试直接转换为基础(如果它不是抽象的)并且相应的 ctor 不可访问,则甚至可以进行 OR 转换。存在差异的情况是当您尝试在 SFINAE 上下文中执行此操作时 - SFINAE 将捕获访问冲突(正如您在问题中指出的那样,只有“直接”访问冲突,而不是嵌套在启动实例中),与重载决议相反。从这个 POV 看,检测和不检测隐私都会偏离“正确”的行为:)
  • 解释:SFINAE 上下文中的类型实例化不能直接使用,因此 Johannes 的解决方案利用了函数重载解决方案,该解决方案对访问冲突或抽象类型不敏感。如果可以从 U... 构造 T 忽略访问冲突和抽象类型,那么 check1 的两个重载都变得可用并且是不明确的,所以 checkno 重载被禁用并且构造函数被启用.否则,只有check 的一个重载可用,所以no 被启用和首选(通过它的第一个int 参数)并且构造函数被禁用。对吗?
猜你喜欢
  • 2010-11-08
  • 2013-11-13
  • 2015-11-17
  • 1970-01-01
  • 2014-08-29
  • 2013-08-29
  • 2013-10-06
  • 2011-05-30
  • 2019-11-18
相关资源
最近更新 更多