2

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.

Community
  • 1
  • 1
Freddie Chopin
  • 8,440
  • 2
  • 28
  • 58

1 Answers1

7

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
T.C.
  • 133,968
  • 17
  • 288
  • 421
  • Wow, perfect solution for my case (; Do you by any chance know of anything that could be used so easily as an argument for `std::enable_if<>`? With an API similar to this - `has_function<&Container::erase(std::forward(args)...)>::value`? – Freddie Chopin Jan 09 '15 at 10:42
  • I know of an indirect way. Have a look at my library, in which I use this often: https://bitbucket.org/leemes/fn/src/9e2c23be0d7979c15269e9ee6f2e8202e33af7ef/traits.h?at=master -- In line 57 you see a macro which defines a helper class to test for such a function, in line 80 I call that macro to create some helper classes for particular function names, e.g. `has_size` in line 85. I then use these like `enable_if::value, ...>` – leemes Jan 09 '15 at 11:24
  • @fred note there is an obscure bit of undefined behavior lurking here. If a template function has no legal instantiations, the program is ill formed, with no diagnostic required. In practice this does not cause problems: the issue is that in future versions of C++ more checks to the validity of template code could be added, and any function that has no valid instantiation is fair game to start breaking your build. In theory, the problem is worse. – Yakk - Adam Nevraumont Jan 09 '15 at 12:40
  • @Yakk - isn't `std:enable_if<>` in the same "undefined behavior" category in that case? From what I understand the construct presented by T.C. in his answer above works more-or-less in the same way as `std::enable_if<>`... – Freddie Chopin Jan 09 '15 at 13:29
  • @leemes - thx for the link. I was hoping there's a solution that doesn't require the macro to create multiple classes ); – Freddie Chopin Jan 09 '15 at 13:30
  • @fred no: `enable_if` involves no functions. Usually a function using SFINAE with `enable_if` **has** at least one valid specialization that compiles: here, it is not the case (unless you write them very carefully) – Yakk - Adam Nevraumont Jan 09 '15 at 14:13
  • @Yakk I'm not convinced. Instantiating `SortedContainer, std::less>` instantiates only the declarations of its member templates, not the definitions, and there's nothing wrong with those declarations as altered in my answer. The definitions may pose a problem, but they are never instantiated as long as you don't use them. – T.C. Jan 09 '15 at 20:34