0

I have the following smart_ptr class.

template <typename T>
class smart_ptr
{
public:
    // ... removed other member functions for simplicity
    T* get() { return ptr; }

    template <typename... Args>
    decltype(T::template operator ()(Args()...)) operator ()(Args... args) const
    {
        return (*get()).operator ()(args...);
    }

private:
    T* ptr;
};

However, when I use the smart_ptr class for type T without an operator (), it fails to compile (and rightly so).

What I wanted is to use std::enable_if to enable the member function only when T has operator (). It got obscure and complex very fast when I got into doing it, as I was thinking of using SFINAE to detect if T has operator () and then combine it with std::enable_if to get what I want. However, I got lost in creating SFINAE that is capable of detecting a variadic templated operator () (i.e. NOT a duplicate of Is it possible to write a template to check for a function's existence?).

Can anyone help? Or give another (perhaps simpler) solution?

p.s. It has to work on GCC 4.5.3.

EDIT: For the sake of completeness, I've answered my own question with a working solution for GCC 4.5.3.

Community
  • 1
  • 1
Zach Saw
  • 4,130
  • 3
  • 30
  • 46
  • 1
    Specifically, check [my answer](http://stackoverflow.com/a/9154394/500104), which shows how it can be easily done with the trailing return type: `auto operator()(Args... args) -> decltype((*get())(args...)){ ... }`. Question, though: Why restrict to `operator()`? If `T` defines a conversion to function pointer, you can get so-called [*surrogate call functions*](http://stackoverflow.com/q/8873048/500104), and although they may not be common, it's best to be generic (and it looks neater). – Xeo Nov 27 '12 at 23:33
  • Xeo: How is it a duplicate? This is a function with variadic template, not a straight forward function which I've already got to know a few years back! – Zach Saw Nov 27 '12 at 23:42
  • 'it fails to compile (and rightly so)'. Hm, should the compiler not postpone instantiation of class template members to the point where they are _used_? That would mean it should not fail to compile unless you actually tried to _use_ the `operator()` **or** you somehow force complete template instantiation? – sehe Nov 27 '12 at 23:45
  • @sehe: It should not fail to compile unless you use it on T where T does not have operator(). – Zach Saw Nov 27 '12 at 23:46
  • @Xeo: Care to give an answer? – Zach Saw Nov 27 '12 at 23:47
  • @sehe: It fails to compile in the example because the compiler checks the `T::template operator ()` syntax, if you remove that the compile will ignore it. – Jesse Good Nov 27 '12 at 23:47
  • @JesseGood wokay, that's stating the obvious. However, you made me realize what I was missing, the problem with this member template is, is that it will fail even on _shallow_ instantiation (i.e. on ODR-use) because it expands to an ill-formed expression in the _return type_ (i.e. before the member template ever gets instantiated). – sehe Nov 27 '12 at 23:50
  • @Xeo: A trailing return type does not solve the problem. – Zach Saw Nov 27 '12 at 23:51
  • @Zach: And what exactly is the problem with it being a variadic function? All you need to know is whether the expression `(*get())(args...)` compiles, and that is what the `decltype` does. – Xeo Nov 27 '12 at 23:52
  • @Xeo: GCC error: sorry, unimplemented: use of ‘type_pack_expansion’ in template – Zach Saw Nov 27 '12 at 23:53
  • 1
    @Zach: Well, you *do* know that 4.5 generally had bad variadic template support, right? Any reason you can't update the compiler? – Xeo Nov 27 '12 at 23:59
  • 1
    @Xeo: The question explicitly said for GCC 4.5.3. If it's not possible with it, then answer and say it is not possible. – Zach Saw Nov 27 '12 at 23:59
  • @Xeo: This still doesn't work without the GCC bug: http://ideone.com/31Yz2A – Zach Saw Nov 28 '12 at 00:06
  • @Zach: That's another GCC bug, it should SFINAE out there. :) – Xeo Nov 28 '12 at 00:11
  • @Xeo: Can't upgrade GCC because I'm targeting default Cygwin installation too. – Zach Saw Nov 28 '12 at 00:11
  • @Xeo: Yes there are numerous GCC bugs but I'm after a solution that works around them. – Zach Saw Nov 28 '12 at 00:12
  • @Xeo: Way to go jumping to conclusion that this is a duplicate when you can't even work out a solution! – Zach Saw Nov 28 '12 at 00:15
  • @Zach : My my, such entitlement. Poor form, sir. – ildjarn Nov 28 '12 at 04:24

2 Answers2

2

Edited: completely different from the original answer, which did not give a real solution.

This code compiled on ideone which uses gcc 4.5.1. It's the best I can do.

#include <array>
#include <iostream>

using namespace std;

template<typename T>
struct has_property
{
    struct UnmentionableType { UnmentionableType() {} };

    //test if type has operator() (...)
    template<typename U, typename A1, typename A2, typename A3, typename A4, typename A5>
    static auto ftest(U* t, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5) -> decltype((*t)(a1, a2, a3, a4, a5), char(0));
    static std::array<char, 2> ftest(...);

public:
    static const bool value = sizeof(ftest((T*)0, UnmentionableType(), UnmentionableType(), UnmentionableType(), UnmentionableType(), UnmentionableType())) == 1;
};

class c1
{
public:
    int operator() () {  return 0; }
};

class c2
{
public:
    void operator() (int) { }
};

class c3
{
public:
    template<typename... Args>
    void operator() (Args... a) { }
};

class c4
{
public:
    template<typename T>
    void operator() (T t) { }
};

int main()
{
    cout<<boolalpha<<has_property<c1>::value<<endl;
    cout<<boolalpha<<has_property<c2>::value<<endl;
    cout<<boolalpha<<has_property<c3>::value<<endl;
    cout<<boolalpha<<has_property<c4>::value<<endl;
}

output:

false
false
true
false

Notes:

1) As requested, it only triggers on variadic templated operator() (well, almost)

2) It's not a "perfect" solution, but it should be good enough for practical use.

Unfortunately gcc 4.5.1 can't expand parameter packs; I suspect this might work for you:

template<typename U, typename... A>
static auto ftest(U* t, A... a) -> decltype((*t)(a...), char(0));

I still don't see a way to actually TEST the true variadicity of the function, as opposed to just passing it 5 (or more) random-type parameters.

Usage with_enable if - I don't see any other solution than partial class specialization:

//non-specialized
template<typename T, typename E=void>
class container
{
    T* ptr;

public:
    T* get() { return ptr; }
};

//specialization for class with appropriate operator()
template<typename T>
class container<T, typename std::enable_if<has_property<T>::value, void>::type>
{
    T* ptr;

public:
    T* get() { return ptr; }

    template <typename... Args>
    decltype(T::template operator ()(Args()...)) operator ()(Args... args) const
    {
        return (*get()).operator ()(args...);
    }
};
Andrei Tita
  • 1,166
  • 8
  • 12
  • My question was specifically ''for a variadic template operator()'' – Zach Saw Nov 27 '12 at 23:45
  • And not to mention GCC 4.5.3 – Zach Saw Nov 28 '12 at 00:15
  • Fair enough. I've edited my answer to something that is (hopefully) useful. – Andrei Tita Nov 28 '12 at 00:46
  • Thank you for your answer. This is very close to what I want. Could you also use enable_if with your solution to demonstrate how I can achieve what I'm after? Especially the second template arg of enable_if -- I do not know what to pass it without getting the compiler to fail on type T that does not declare `operator ()`. – Zach Saw Nov 28 '12 at 00:55
  • Given your existing code, I don't see any other solution than class template specialization, which would involve either rewriting the whole class for the two cases, or inheritance; is that acceptable? The reason I don't think you could use function template specialization for operator() is that it doesn't have a fixed return type so I don't see how to overload based on signature. Maybe there is a solution. – Andrei Tita Nov 28 '12 at 01:24
1

Finally, I've found a workaround for GCC 4.5.3!

template <typename T>
class smart_ptr
{
public:
    // ... removed other member functions for simplicity
    T* get() const { return ptr; }

private:
    template <typename... Args>
    struct opparen_constret
    {
        typedef decltype(std::declval<T const>()(std::declval<Args>()...)) type;
    };

    template <typename... Args>
    struct opparen_ret
    {
        typedef decltype(std::declval<T>()(std::declval<Args>()...)) type;
    };

public:
    template <typename... Args>
    typename opparen_constret<Args...>::type operator ()(Args... args) const
    {
        return (*get())(args...);
    }

    template <typename... Args>
    typename opparen_ret<Args...>::type operator ()(Args... args)
    {
        return (*get())(args...);
    }

private:
    T* ptr;
};
Zach Saw
  • 4,130
  • 3
  • 30
  • 46