Freddie Chopin
Freddie Chopin

Reputation: 8860

Simple way to selectively disable template member in C++11

Let's start with a code (please note that is's shortened to show the problematic parts only).

#include <type_traits>
#include <utility>
#include <list>
#include <forward_list>
#include <functional>

template<typename Container, typename Compare>
class SortedContainer
{
public:

    template<typename... Args>
    auto erase(Args&&... args) -> decltype(std::declval<Container>().erase(std::forward<Args>(args)...))
    {
        return container_.erase(std::forward<Args>(args)...);
    }

    decltype(std::declval<const Container>().size()) size() const
    {
        return container_.size();
    }

private:

    /// container used for keeping elements
    Container container_;
};

SortedContainer<std::list<int>, std::less<int>> list;
SortedContainer<std::forward_list<int>, std::less<int>> forward_list;

int main()
{

}

I'd like to selectively disable some functions from the SortedContainer template in case the underlying Container doesn't have the required function with matching signature.

The example above fails, because std::forward_list doesn't have erase() and size() functions:

$ g++ -std=c++11 test.cpp 
test.cpp: In instantiation of ‘class SortedContainer<std::forward_list<int>, std::less<int> >’:
test.cpp:30:57:   required from here
test.cpp:13:7: error: ‘class std::forward_list<int>’ has no member named ‘erase’
  auto erase(Args&&... args) -> decltype(std::declval<Container>().erase(std::forward<Args>(args)...))
       ^
test.cpp:18:51: error: ‘const class std::forward_list<int>’ has no member named ‘size’
  decltype(std::declval<const Container>().size()) size() const
                                                   ^

I cannot use std::enable_if with std::is_member_function_pointer, because erase() is overloaded. Maybe some clever casting would help with that?

I hoped that it would "just work", as this is very similar to the last solution from this answer https://stackoverflow.com/a/9531274/157344 , but it doesn't, even if I give type (typename Container::iterator()) after the comma in the decltype - the error message is identical.

I was hoping that there is some nice solution that doesn't require implementing a dozen of has_*() constexpr functions to check whether or not this particular function exists. I couldn't find any example of "check whether this member with this arguments exists" template that was flexible, so it could work like:

has_function<&Container::erase(std::forward<Args>(args)...)>::value
has_function<&Container::size()>::value

without need to implement separate has_erase() and has_size().

I'd also prefer NOT to specialize the SortedContainer class for std::forward_list, because I don't care what the actual type is, just whether or not it has the functions needed for forwarding wrappers.

Upvotes: 2

Views: 408

Answers (1)

T.C.
T.C.

Reputation: 137330

Instead of using Container directly, add an extra template parameter with Container as the default argument and use that:

template<typename... Args, typename C = Container>
//                       ^^^^^^^^^^^^^^^^^^^^^^^^
auto erase(Args&&... args) -> decltype(std::declval<C>().erase(std::forward<Args>(args)...))
//                                                  ^

Ditto for size():

template<typename C = Container>
decltype(std::declval<const C>().size()) size() const

Upvotes: 7

Related Questions