如何创建一个只在其类型具有特定成员函数时才编译的类?

How do I make a class that only compiles when its type has a certain member function?

本文关键字:函数 成员 编译 类型 创建 何创建 一个      更新时间:2023-10-16

我有一个名为has_f的类,我希望它只接受具有f成员函数的模板参数。我该怎么做呢?这是我尝试过的:

template <typename T, typename = void>
struct has_f : std::false_type {};
template <typename T>
struct has_f<
    T,
     typename = typename std::enable_if<
        typename T::f
    >::type
> : std::true_type {};

但是我得到一些隐晦的错误。下面是我想使用的类:

struct A
{
    void f();
};

我如何正确地做到这一点?谢谢。

从你的问题的标题,我假设你并不真的需要一个类型派生自true_type或谬误-只是为了防止编译如果方法f不存在。如果是这种情况,并且您还需要该方法的特定签名(至少在参数方面),那么在c++ 11中可以这样做:

template <typename T>
struct compile_if_has_f
{
    static const size_t dummy = sizeof(
        std::add_pointer< decltype(((T*)nullptr)->f()) >::type );
};

这适用于f()不应该接受任何参数的情况。Std::add_pointer只有在f返回void时才需要,因为sizeof(void)是非法的。

可能的复制品检查类是否具有给定签名的成员函数。我也没有改变主意。

我认为这个问题可以解压缩到A)如何检查类是否具有给定签名的成员函数和B)如何坚持一个类模板参数是一个类A)"。在这种情况下,我会用static_assert来回答B),因为提问者显然对enable_if选项不感兴趣。

这是一个使我的答案适应的解决方案"用于测试func(args)是否格式良好并具有所需返回类型的特征"这个解决方案假设has_f<T>::value当且仅为真如果public成员void T::f()存在,即使T重载f或继承f

#include <type_traits>
template<typename T>
struct has_f
{   
    template<typename A>
    static constexpr bool test(
        decltype(std::declval<A>().f()) *prt) {
        return std::is_same<void *,decltype(prt)>::value;
    }
    template <typename A>
    static constexpr bool test(...) {
        return false; 
    }
    static const bool value = test<T>(static_cast<void *>(nullptr)); 
};
// Testing...
struct i_have_f
{
    void f();   
};
struct i_dont_have_f
{
    void f(int);    
};
struct i_also_dont_have_f
{
    int f();    
};
struct i_dont_quite_have_f
{
    int f() const;  
};
struct i_certainly_dont_have_f
{};
struct i_have_overloaded_f
{
    void f();
    void f(int);
};
struct i_have_inherited_f : i_have_f
{};
#include <iostream>

template<typename T>
struct must_have_f{
    static_assert(has_f<T>::value,"T doesn't have f");
};
int main()
{
    must_have_f<i_have_f> t0; (void)t0;
    must_have_f<i_have_overloaded_f> t1; (void)t1;
    must_have_f<i_have_inherited_f> t2; (void)t2;
    must_have_f<i_dont_have_f> t3; (void)t3; // static_assert fails
    must_have_f<i_also_dont_have_f> t4; (void)t4; // static_assert fails
    must_have_f<i_dont_quite_have_f> t5; (void)t5; // static_assert fails
    must_have_f<i_certainly_dont_have_f> t6; (void)t6; // static_assert fails
    must_have_f<int> t7; (void)t7; // static_assert fails
    return 0;
}

(由3.2叮当声,gcc 4.7.2/4.8.1)

在回答你的问题和为你的问题提供解决方案而不是直接回答你的问题之间有一条微妙的界限,但我认为这可能会对你有所帮助。

有关背景,请查看这个问题。作者提到他不喜欢Boost的解决方案,我也不是特别喜欢那里提出的解决方案。我正在写一篇简短的文章。脏序列化库(想想python的marshal),你可以在对象上调用serialize(object, ostream)来序列化它。我意识到我希望这个函数调用到以下四个对象之一:

  1. 如果object是普通的旧数据,只需写出大小和原始数据
  2. 如果object是我用自己的成员函数(object::serialize)创建的类,那么调用该成员函数
  3. 如果有模板专门化的类型,使用它。
  4. 如果以上都不为真,则抛出编译错误;序列化函数使用不正确。

当我写代码的时候,我尽量避免那些"棘手的"或者一眼就看不懂的东西。我认为这个解决方案可以解决同样的问题,而不需要使用必须思考几个小时才能理解的代码:

#include <type_traits>
#include <iostream>
#include <vector>
#include <string>
// Template specialization for a POD object
template<typename T> 
typename std::enable_if< std::is_pod<T>::value, bool>::type
serial(const T &out, std::ostream &os)
{
    os.write((const char*) &out, sizeof(T));
    return os.good();
}
// Non POD objects must have a member function 'serialize(std::ostream)'
template<typename T> 
typename std::enable_if< ! std::is_pod<T>::value, bool>::type
serial(const T &out, std::ostream &os)
{
    return out.serial(os);
}
// Additional specializations here for common container objects
template<typename T> 
bool serial(const std::vector<T> &out, std::ostream &os)
{
    const size_t vec_size = out.size();
    if(!serial(vec_size, os))
        return false;
    for(size_t i =0; i < out.size(); ++i)
    {
        if(!serial(out[i], os))
            return false;
    }
    return true;
}
class SomeClass
{
    int something;
    std::vector<double> some_numbers;
    ...
    bool serial(std::ostream &os)
    {
        return serial(something, os) && serial(some_numbers, os);
    }
};

如果你可以把你的需求归结为一组简单的规则,并且可以接受一个稍微不那么通用的解决方案,我认为这个方法很好。