如何检查一个类是否有默认构造函数,无论是公共的、受保护的还是私有的。

11

我需要检查类C是否具有默认构造函数,可以是隐式的也可以是自定义的,并且可以是publicprotectedprivate之一。

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

有没有办法检查一个类是否有protectedprivate默认构造函数?

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


我需要执行此检查以便对Foo对象的m_objs元组的nullptr指针进行默认构造(以下是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<B>::value为false,即使B有一个[私有]默认构造函数,并且Foo<A,B>是B的友元。


@SergeyA:我没有想到那个技巧,很好的解决方案,谢谢;不过还需要私有内容,无论如何还是感谢。 - shrike
不行,因为我不知道这有什么用处。 - Piotr Skotnicki
1
请问您能否发布友元函数的签名吗? - linuxfever
@linuxfever: 我可以这样做,但会非常痛苦,因为我提供的示例只是我的代码的一个非常简化的版本;而问题与友谊无关:您可以轻松检查使用具有私有默认构造函数和函数 void foo()B 的朋友的基本类 B 来证明这一点,std::is_default_constructible<B>::valuefoo() 内返回 false - shrike
@Serge:请看我对linuxfever的评论。 - shrike
显示剩余5条评论
3个回答

6

我将提供一个简化的例子,让事情变得更容易。然后你可以将其适应到你的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;
}

刚刚测试了你的代码片段,非常好用!我会尽快将其包含在我的实际应用中。非常感谢! - shrike

3

问题在于,如果一个类既没有公有的、也没有保护的、也没有私有的默认构造函数,那么简单的默认实例定义会导致编译错误,而不是运行时异常。因此,测试很简单:如果友元函数中的表达式 C c; 编译通过,则该类有一个默认构造函数,可以是公有的、受保护的或私有的。

请看以下示例代码:

#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文件以添加来自另一个源的类的“友元”函数(但不是库),并仍然使其全部链接OK?似乎非常依赖编译器。 - Kevin Anderson
谁说过什么关于运行时? - Barry
@Serge:好的,我没有详细说明我需要执行此检查的上下文,基本上它是在一个可变模板函数内执行的,该函数具有由多个类组成的参数包,并且其中一些类没有默认构造函数但仍然是合法的。 - shrike
@Barry:在我看来,测试隐含着运行时间。 - Serge Ballesta
@Serge:测试不需要在运行时执行,但不应该发出编译器错误;请参见我上面的编辑。 - shrike
@Barry:测试不需要在运行时执行,但是它不应该发出编译器错误;请参见我上面的编辑。 - shrike

3

我理解您想要检查您的朋友是否可以默认构造类。诀窍在于您必须在friend函数的作用域内使用SFINAE,在此之前,您需要使用模板。要使用本地模板,您需要通用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 lambda表达式(需要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()`内部的`Q`(即`typename 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'd 静态成员函数。这需要 @shrike 来回答。 - MSalters
@MSalters 为什么?只要类模板是友元,这个方法就非常好用。 - Barry
@Barry:我很乐意在我的代码中测试你的解决方案,但是我的&%#@ msvc14编译器在lambda定义中的decltype上崩溃了,导致致命错误C1001... :( - shrike
@shrike MSVC不支持表达式SFINAE,所以你有点运气不好。 - Barry
@Barry:好的...有一天会考虑迁移到gcc...无论如何,感谢你的帮助。 - shrike

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