【问题标题】:How to check if a class has a default constructor, either public, protected or private如何检查一个类是否具有默认构造函数,无论是公共的、受保护的还是私有的
【发布时间】:2016-05-14 00:07:34
【问题描述】:

我需要检查一个类 C 是否具有默认构造函数,无论是隐式的还是自定义的,以及 publicprotectedprivate

我尝试使用std::is_default_constructible<C>::value,如果C 具有public 默认构造函数(隐式或自定义)则返回true,但false 如果C 具有protectedprivate 默认构造函数(不过,接缝似乎是标准行为。)

有什么方法可以检查一个类是否有protectedprivate 默认构造函数?

注意(如果这可能有帮助):检查是从要检查的 C 类的 friend 函数执行的。


我需要执行此检查,以便默认构造与 m_objs 元组的 nullptr 指针相对应的对象,Foo 对象的成员(下面的部分 Foo 定义):

template<class... Objects>
class Foo
{
public:
    Foo(Objects*... objects)
    : m_objs(objects...)
    {
        // User construct a Foo objects passing a pack of pointers
        // some of them are nullptr, some are not.
        // The following call should default-construct objects corresponding
        // to the null pointers of m_objs member tuple:
        objs_ctor<Objects...>();
    }
private:
    template<class Obj, class O1, class ...On>
    void objs_ctor()
    {
        objs_ctor<Obj>(); objs_ctor<O1, On...>();
    }
    template<class Obj>
    typename std::enable_if<std::is_default_constructible<Obj>::value, void>::type
    objs_ctor()
    {
        Obj*& obj = std::get<Obj*>(m_objs);
        if (obj == nullptr)
            obj = new Obj;    // default-construct Obj
    }
    template<class Obj>
    typename std::enable_if<!std::is_default_constructible<Obj>::value, void>::type
    objs_ctor()
    {
        Obj*& obj = std::get<Obj*>(m_objs);
        assert(obj != nullptr);   // terminate if not pre-constructed
    }

private:
    std::tuple<Objects*...>     m_objs;
};

打算用作:

struct A { };
class B {
    B() = default;

    template <class... Ts>
    friend class Foo;
};

int main() {
    // currently asserts, even though Foo<A,B> is a friend of B
    // and B is default-constructible to its friends
    Foo<A, B> foo(nullptr, nullptr);
}

上面的示例断言因为std::is_default_constructible&lt;B&gt;::value 为假,即使B 有一个[私人] 默认 ctor 并且Foo&lt;A,B&gt; 是 B 的朋友。

【问题讨论】:

  • 易于检查受保护 - 只需从它继承并查看新人是否默认可构造。我不知道如何为私人做到这一点。
  • 你检查过this post吗?声明“即使默认构造函数是私有的或受保护的,is_default_constructible 也会返回 true”,并提供了许多解决方案。返回值似乎取决于您使用的编译器的版本。此外,如果执行检查的类是 C 的朋友,您可能需要查看 workaround
  • @SergeyA:我没想过这个技巧,很好的解决方案,谢谢;不过,仍然需要一些私人的东西;无论如何,谢谢。
  • @much_a_chos:我看过这个帖子; afaiu,这是一个错误(在我使用的编译器版本中修复),OP 需要解决。
  • 可以把朋友功能的签名贴出来吗?

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


【解决方案1】:

我将提供一个简化的示例以使事情变得更容易。然后你可以将它调整到你的 Foos 类。这个想法是专门化我的模板化朋友类如下

#include <iostream>    

// forward declaration
template <class... T>
struct Friend;

struct testing_tag;

// specialisation simply to check if default constructible
template <class T>
struct Friend<T, testing_tag> {

  // sfinae trick has to be nested in the Friend class
  // this candidate will be ignored if X does not have a default constructor
  template <class X, class = decltype(X())>
  static std::true_type test(X*);

  template <class X>
  static std::false_type test(...);

  static constexpr bool value = decltype(test<T>(0))::value;
};

请注意,只要 X 具有默认构造函数,无论它是私有的、受保护的还是公有的,std::true_type 候选者都将始终有效。现在测试一下

class default_public {

  template <class... T>
  friend struct Friend;

};

class default_protected {

  template <class... T>
  friend struct Friend;

protected:
  default_protected() {}
};

class default_private {

  template <class... T>
  friend struct Friend;

private:
  default_private() {}

};

class no_default {

public:
  no_default(int x){}
};

// a convenient name to test with
template <class T>
using has_any_default_constructor = Friend<T, testing_tag>;

int main() {
  std::cout << has_any_default_constructor<default_public>::value << std::endl;
  std::cout << has_any_default_constructor<default_protected>::value << std::endl;
  std::cout << has_any_default_constructor<default_private>::value << std::endl;
  std::cout << has_any_default_constructor<no_default>::value << std::endl;
}

【讨论】:

  • 刚刚测试了您的 sn-p,效果很好!我将尽快将其包含在我的实际应用程序中。非常感谢!
【解决方案2】:

问题在于,如果一个类既没有公共的、保护的也没有私有的默认构造函数,实例的简单默认定义会给出编译错误,而不是运行时异常。所以测试很简单:如果友元函数C c;中的这个表达式编译,则该类有一个默认的ctor,可以是public、protected或private。

只看示例代码:

#include <iostream>
#include <string>

class Cwith {
private:
    std::string name;
    Cwith(): name("default ctor") {}
    friend void build();
};

class Cwithout {
private:
    std::string name;
    Cwithout(const std::string& name): name(name) {};
    friend void build();
};

void build() {
    Cwith cw;
    std::cout << cw.name << std::endl;
    Cwithout cwo;   // error : class Cwithout has no defaut constructor
    std::cout << cwo.name << std::endl;
}

int main() {
    build();
    return 0;
}

但我无法想象运行时测试...

【讨论】:

  • 旁注:friend void build() 是否算作编译目的的原型,还是必须在朋友声明之前前向声明函数?就是想。不幸的是,我认为这不适用于如果不重新编译就无法控制的类。您可以更改 .h 文件以添加来自另一个来源的类的 friend 函数(但不是库),并且仍然可以将其全部链接吗?似乎非常依赖编译器。
  • 谁说过关于运行时的事?
  • @Serge:好的,我没有详细说明我需要执行此检查的上下文,但基本上它是在一个可变参数模板函数中执行的,该函数由几个类组成的参数包,其中一些没有默认构造函数,但虽然是合法的。
  • @Barry:据我了解,测试是隐式运行时
  • @Serge:测试不需要在运行时执行,但它不应该发出编译器错误;见我上面的编辑。
【解决方案3】:

我的理解是你想检查你的friend 是否可以默认构造一个类。诀窍是您必须将 SFINAE 置于 friend 函数的范围内。为此,您需要模板。要拥有本地模板,您需要通用 lambda。

我们从一个可以传递多个 lambda 表达式的重载器开始。可能有更好的写法:

template <class... Fs>
struct overload {
    void operator()();
};

template <class F, class... Fs>
struct overload<F, Fs...> : F, overload<Fs...> {

    overload(F&& f, Fs&&... fs)
    : F(std::forward<F>(f))
    , overload<Fs...>(std::forward<Fs>(fs)...)
    { }

    using F::operator();
    using overload<Fs...>::operator();
};

template <class... Fs>
overload<std::decay_t<Fs>...> make_overload(Fs&&... fs) {
    return {std::forward<Fs>(fs)...};
}

然后传入重载、通用、SFINAE-d lambdas(需要 C++14)。假设我们有:

struct Q {
    friend void foo();
};

template <class T>
struct tag_t { using type = T; };

template <class T>
constexpr tag_t<T> tag{};

那么我们可以这样写:

void foo() {
    auto f = make_overload(
        [](auto x) -> decltype( typename decltype(x)::type(), void() ) { 
            Q q;
            std::cout << "I can do it.\n";
        },
        [](...) {
            std::cout << "What do you want here?\n";
        });

    f(tag<Q>, 0); 
}

如果works 是朋友,那么第一个重载是可行的并且是首选的——所以你明白了,它构造了一个Q,因为它可以。如果works 不是朋友,则第一个重载不可行,因此您将获得第二个重载。

关键是我们在works() 中测试Qtypename decltype(x)::type() 部分)的构造——所以它要么被友谊覆盖。


所以对于你的具体用法,那就是:

template <class Obj>
objs_ctor() {
    Obj*& obj = std::get<Obj*>(m_objs);

    auto ctor = make_overload(
        [&](auto x) -> decltype( typename decltype(x)::type(), void() ) { 
            if (!obj) {
                obj = new Obj;
            }
        },
        [=](...) {
            assert(obj);
        });

    ctor(tag<Obj>);
}

【讨论】:

  • 一个相关的问题是 friend 是否实际上可以成为 SFINAE 的已结识类的统计成员函数。这是@shrike 的回答。
  • @MSalters 为什么?只要类模板是友好的,这就很好用。
  • @Barry:我很高兴在我的代码中测试您的解决方案,但是我的 &%#@ msvc14 编译器在 lambda 定义中的 decltype 上出现致命错误 C1001 崩溃... :(
  • @shrike MSVC 不支持表达式 SFINAE,所以你有点不走运。
  • @Barry: 好的...有一天会考虑迁移到 gcc...无论如何感谢您的帮助。
猜你喜欢
  • 2013-08-14
  • 2021-01-10
  • 1970-01-01
  • 2016-03-27
  • 2011-05-08
  • 2014-07-25
  • 2011-05-29
  • 2019-08-24
  • 1970-01-01
相关资源
最近更新 更多