4

Lets say I have

struct foo {
    void ham() {}
    void ham() const {}
};

struct bar {
    void ham() {}
};

Assuming I have a templated function, can I tell whether given type has a const overload for ham?

vsoftco
  • 55,410
  • 12
  • 139
  • 252
Xarn
  • 3,460
  • 1
  • 21
  • 43
  • 5
    What are you trying to accomplish? What is the use case for this? – NathanOliver Feb 11 '16 at 19:41
  • @NathanOliver It is a long story, but the basic idea is to have an automatic checker with friendlier error messages than compilation errors, while minimizing the amount of separate compilations I would have to do. – Xarn Feb 11 '16 at 19:46
  • OK. It looked like an [XY problem](http://meta.stackexchange.com/questions/66377/what-is-the-xy-problem) to me so I figured I would ask. – NathanOliver Feb 11 '16 at 19:49
  • @NathanOliver Sure, not a problem. – Xarn Feb 11 '16 at 20:04

5 Answers5

6

With

#define DEFINE_HAS_SIGNATURE(traitsName, funcName, signature)               \
    template <typename U>                                                   \
    class traitsName                                                        \
    {                                                                       \
    private:                                                                \
        template<typename T, T> struct helper;                              \
        template<typename T>                                                \
        static std::uint8_t check(helper<signature, &funcName>*);           \
        template<typename T> static std::uint16_t check(...);               \
    public:                                                                 \
        static                                                              \
        constexpr bool value = sizeof(check<U>(0)) == sizeof(std::uint8_t); \
    }

DEFINE_HAS_SIGNATURE(has_ham_const, T::ham, void (T::*)() const);

And then

static_assert(has_ham_const<foo>::value, "unexpected");
static_assert(!has_ham_const<bar>::value, "unexpected");

Demo

Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • No way without using an ugly macro? – πάντα ῥεῖ Feb 11 '16 at 19:49
  • 1
    Yes, oldschool SFINAE which checks the *exact* signature. What I sometimes asked myself: can it also be extended to arbitrary return types (with no expression sfinae a la `void_t`)? – davidhigh Feb 11 '16 at 19:51
  • Thanks, that works, but I second davidhigh's question. Can this be extended to allow for differing return types? – Xarn Feb 11 '16 at 20:09
  • @davidhigh See my answer for a version that does not care about the return type. – Rumburak Feb 11 '16 at 20:13
  • @Rumburak: yes, but that's expression sfinae. – davidhigh Feb 11 '16 at 20:16
  • @davidhigh Oops, sorry, did not read your comment carefully enough. In my defense: It seems to be the motto of the day, judging by the two other answers in the same style that came in within a few seconds later :-) – Rumburak Feb 11 '16 at 20:21
  • @vsoftco: me too, but tell that [Microsoft](https://blogs.msdn.microsoft.com/vcblog/2015/12/02/partial-support-for-expression-sfinae-in-vs-2015-update-1/). – davidhigh Feb 11 '16 at 20:25
3

Detector (like is_detected):

template <typename...>
using void_t = void;

template <typename T, template <typename> class D, typename = void>
struct detect : std::false_type {};

template <typename T, template <typename> class D>
struct detect<T, D, void_t<D<T>>> : std::true_type {};

Sample member verifier:

template <typename T>
using const_ham = decltype(std::declval<const T&>().ham());

Test:

static_assert(detect<foo, const_ham>::value, "!");
static_assert(!detect<bar, const_ham>::value, "!");

DEMO

Piotr Skotnicki
  • 46,953
  • 7
  • 118
  • 160
3

SFINAE over and over again. Here is another option which is unspecified about the return types but lets you specify the arguments.

(For comparison: the approach by @Jarod42 checks the exact signature, return type + arguments, the other void_t expression sfinae stuff up to now checks only whether ham() can be called.)

Plus, it works with the current MSVC 2015 Update 1 version (unlike the usual void_t stuff).

template<typename V, typename ... Args>
struct is_callable_impl
{
    template<typename C> static constexpr auto test(int) -> decltype(std::declval<C>().ham(std::declval<Args>() ...), bool{}) { return true; }
    template<typename> static constexpr auto test(...) { return false; }
    static constexpr bool value = test<V>(int{});
    using type = std::integral_constant<bool, value>;
};

template<typename ... Args>
using is_callable = typename is_callable_impl<Args...>::type;

Use it as

struct foo
{
    void ham() {}
    void ham() const {}
    int ham(int) const {}
};

int main()
{
     std::cout
      <<is_callable<foo>::value                //true
      <<is_callable<const foo>::value          //true
      <<is_callable<const foo, int>::value     //true
      <<is_callable<const foo, double>::value  //also true, double is converted to int
      <<is_callable<const foo, std::string>::value  //false, can't call foo::ham(std::string) const
      <<std::endl;
}

Demo on Coliru

For the "newest" sfinae stuff, however, I suggest you have a look at boost.hana.

Piotr Skotnicki
  • 46,953
  • 7
  • 118
  • 160
davidhigh
  • 14,652
  • 2
  • 44
  • 75
  • I ended up accepting this answer because of MSVC compatibility. – Xarn Feb 12 '16 at 17:12
  • With regard to MSVC compability, also have a look at the [new clang plugin](https://blogs.msdn.microsoft.com/vcblog/2015/12/04/clang-with-microsoft-codegen-in-vs-2015-update-1/). That's really cool. – davidhigh Feb 12 '16 at 17:41
2

Another option is to simulate void_t(to appear officially in C++17), which makes use of expression SFINAE to make sure your function is call-able on a const instance, regardless of its return type.

#include <iostream>
#include <type_traits>

struct Foo
{
    void ham() const;
    void ham();
};

struct Bar {
    void ham() {}
};

template<typename...>
using void_t = void;

template<typename C, typename = void>
struct has_const_ham: std::false_type{};

template<typename C> // specialization, instantiated when there is ham() const
struct has_const_ham<C, void_t<decltype(std::declval<const C&>().ham())>> : 
    std::true_type{};

int main()
{
    std::cout << std::boolalpha;
    std::cout << has_const_ham<Foo>::value << std::endl;
    std::cout << has_const_ham<Bar>::value << std::endl;
}

EDIT

If you want to enforce the return type, then derive the specialization from std::is_same, like

template<typename C> // specialization, instantiated when there is ham() const
struct has_const_ham<C, void_t<decltype(std::declval<const C&>().ham())>> : 
    std::is_same<decltype(std::declval<const C&>().ham()), void> // return must be void
{}; 

Live on Coliru

Community
  • 1
  • 1
vsoftco
  • 55,410
  • 12
  • 139
  • 252
  • @davidhigh Thanks! I think all the answers are pretty nice, and probably OP has enough material to digest ;) – vsoftco Feb 11 '16 at 20:54
  • Very pedantic: in `std::declval` the reference is [unneeded](http://en.cppreference.com/w/cpp/utility/declval). – davidhigh Feb 11 '16 at 20:55
  • 1
    @davidhigh Ohh yes, it's an un-evaluated context ;) Well... hmmm... I think `declval()` returns `add_rvalue_reference`, and the latter uses reference collapsing rules. So technically `declval()` results in `const T&`, whereas `declval()` returns `const T&&`. Now, if your function is ref-qualified, it will make a difference. But in any case this is super-duper-detailed. – vsoftco Feb 11 '16 at 20:55
  • completely right, thanks for pointing that out. I always thought it would be `add_*l*value_reference` and therefore never read it carefully. However, then you basically want to call `std::declval` without the reference, as otherwise you loose the chance to question for the ref-qualifier (it will always be `&`). My final comment on this, it's really not that important :-) – davidhigh Feb 11 '16 at 21:15
1

Here is a solution without macros which also does not care about return types:

template <typename T>
struct is_well_formed : std::true_type
{
};

template <typename T, typename = void>
struct has_const_ham : std::false_type
{
};

template <typename T>
struct has_const_ham<T,
                     typename std::enable_if<is_well_formed<decltype(
                         std::declval<const T&>().ham())>::value>::type>
    : std::true_type
{
};


static_assert(has_const_ham<foo>::value, "oops foo");
static_assert(!has_const_ham<bar>::value, "oops bar");
Rumburak
  • 3,416
  • 16
  • 27