4

I have a class named has_f and I want it to only accept template parameters that have a f member function. How would I do that? This is what I tried:

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 {};

But I get some cryptic errors. Here is the class I want to use:

struct A
{
    void f();
};

How do I do this correctly? Thanks.

Me myself and I
  • 3,990
  • 1
  • 23
  • 47

3 Answers3

2

From the title of your question I presume that you don't really need a type deriving from true_type or false_type - only to prevent compilation if method f is not present. If that is the case, and if you also require a specific signature (at least in terms of arguments) for that method, in C++11 you can do something like this:

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

This is for the case when f() should not accept any arguments. std::add_pointer is only needed if f returns void, because sizeof(void) is illegal.

Kirinyale
  • 338
  • 2
  • 10
  • 1
    1) Need `typename std::add_pointer...` to compile. 2) I find that gcc 4.7.2/4.8.1 (unlike clang 3.2) will never barf on `dummy` unless it is evaluated. Need `static_assert`. 3) Also tolerates any return type for `f` – Mike Kinghan Jun 07 '13 at 22:27
  • @MikeKinghan, thanks for the corrections. I tested this code in Visual Studio 2010, that's why I forgot the `typename` (VS does not require it in some cases when other compilers do). As for the return type, I believe we can easily validate it as well by using `std::is_same` and `static_assert`. Unfortunately, there is also a bug in Visual Studio which prevents `static_assert` from working properly at template class scope, so you'll also need to put it inside a method (e.g. constructor). – Kirinyale Jun 08 '13 at 16:29
1

I +1ed rapptz yesterday for
"possible duplicate of Check if a class has a member function of a given signature" and haven't changed my mind.

I suppose it is arguable that this question unpacks to "A) How to check if a class has a member function of a given signature and B) How to insist that a class template argumement is a class as per A)". To B) in this case I would answer with static_assert, since the questioner apparently isn't interested in enable_if alternatives.

Here is a solution that adapts my answer to "traits for testing whether func(args) is well-formed and has required return type" This solution assumes that has_f<T>::value should be true if and only if exactly the public member void T::f() exists, even if T overloads f or inherits 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;
}

(Built with clang 3.2, gcc 4.7.2/4.8.1)

Community
  • 1
  • 1
Mike Kinghan
  • 55,740
  • 12
  • 153
  • 182
0

This toes a fine line between answering your question and providing a solution to your problem but not directly answering your question, but I think you may find this helpful.

For background, check out this question. The author mentions that he didn't like Boost's solution, and I didn't particularly like the one proposed there either. I was writing a quick & dirty serialization library (think python's marshal) where you would call serialize(object, ostream) on an object to serialize it. I realized I wanted this function call to one of four things:

  1. If object is plain old data, just write out the size and raw data
  2. If object is a class that I've created with its own member function (object::serialize), then call that member function
  3. If there's a template specialization for that type, use it.
  4. If none of the above is true, throw a compilation error; the serialize function is being used improperly.

When I code, I try to avoid stuff that is 'tricky' or hard to understand at a glance. I think this solution solves the same problem without using code that must be pondered for hours to understand:

#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);
    }
};

If you can boil down your needs to a simple set of rules, and can live with a slightly less general solution, I think this method works well.

Community
  • 1
  • 1
Nepthar
  • 1,663
  • 1
  • 10
  • 4