35

Okay, simple template question. Say I define my template class something like this:

template<typename T>
class foo {
public:
    foo(T const& first, T const& second) : first(first), second(second) {}

    template<typename C>
    void bar(C& container, T const& baz) {
        //...
    }
private:
    T first;
    T second;
}

The question is about my bar function... I need it to be able to use a standard container of some sort, which is why I included the template/typename C part, to define that container type. But apparently that's not the right way to do it, since my test class then complains that:

error: 'bar' was not declared in this scope

So how would I go about implementing my bar function the proper way? That is, as a function of my template class, with an arbitrary container type... the rest of my template class works fine (has other functions that don't result in an error), it's just that one function that's problematic.

EDIT: Okay, so the specific function (bar) is an eraseInRange function, that erases all elements in a specified range:

void eraseInRange(C& container, T const& firstElement, T const& secondElement) {...}

And an example of how it would be used would be:

eraseInRange(v, 7, 19);

where v is a vector in this case.

EDIT 2: Silly me! I was supposed to declare the function outside of my class, not in it... pretty frustrating mistake to be making. Anyways, thanks everyone for the help, though the problem was a little different, the information did help me construct the function, since after finding my original problem, I did get some other pleasant errors. So thank you!

BenMorel
  • 34,448
  • 50
  • 182
  • 322
Fault
  • 1,239
  • 4
  • 14
  • 18
  • Please provide an example of how you are using the foo class and the foot::bar method. The problem likely is in your using code. – Michael Price Oct 11 '11 at 15:27
  • 2
    The code you posted compiles fine. Where are you actually having the problem? – sth Oct 11 '11 at 15:29
  • Your test class does not happen to *inherit* from foo? See http://www.parashift.com/c++-faq-lite/templates.html#faq-35.19 – UncleBens Oct 11 '11 at 15:33
  • I hope my edit clears it up a little? The test class was provided for the exercise, so I shouldn't change how the method is being called, but it's complaining about the eraseInRange mehtod not being declared... – Fault Oct 11 '11 at 15:36
  • It doesn't. I know the full error message is a long one, but that might help a lot. Or better exact code with a bit of context (which classes are involved). – UncleBens Oct 11 '11 at 15:49
  • 1
    Since we don't know what your test driver is doing, we can't help much. Does the class work *for you*, e.g. if you use `eraseInRange` yourself with a vector? – Luc Danton Oct 11 '11 at 16:18

4 Answers4

45


Traits solution.

Generalize not more than needed, and not less.

In some cases that solution might not be enough as it will match any template with such signature (e.g. shared_ptr), in which case you could make use of type_traits, very much like duck-typing (templates are duck typed in general).

#include <type_traits>

// Helper to determine whether there's a const_iterator for T.
template<typename T>
struct has_const_iterator
{
private:
    template<typename C> static char test(typename C::const_iterator*);
    template<typename C> static int  test(...);
public:
    enum { value = sizeof(test<T>(0)) == sizeof(char) };
};


// bar() is defined for Containers that define const_iterator as well
// as value_type.
template <typename Container>
typename std::enable_if<has_const_iterator<Container>::value,
                        void>::type
bar(const Container &c, typename Container::value_type const & t)
{
  // Note: no extra check needed for value_type, the check comes for
  //       free in the function signature already.
}


template <typename T>
class DoesNotHaveConstIterator {};

#include <vector>
int main () {
    std::vector<float> c;
    bar (c, 1.2f);

    DoesNotHaveConstIterator<float> b;
    bar (b, 1.2f); // correctly fails to compile
}

A good template usually does not artificially restrict the kind of types for which they are valid (why should they?). But imagine in the example above you need to have access to an objects const_iterator, then you can use SFINAE and type_traits to put those constraints on your function.


Or just to as the standard library does

Generalize not more than needed, and not less.

template <typename Iter>
void bar (Iter it, Iter end) {
    for (; it!=end; ++it) { /*...*/ }
}

#include <vector>
int main () {
    std::vector<float> c;
    bar (c.begin(), c.end());
}

For more such examples, look into <algorithm>.

This approach's strength is its simplicity and is based on concepts like ForwardIterator. It will even work for arrays. If you want to report errors right in the signature, you can combine it with traits.


std containers with signature like std::vector (not recommended)

The simplest solution is approximated by Kerrek SB already, though it is invalid C++. The corrected variant goes like so:

#include <memory> // for std::allocator
template <template <typename, typename> class Container, 
          typename Value,
          typename Allocator=std::allocator<Value> >
void bar(const Container<Value, Allocator> & c, const Value & t)
{
  //
}

However: this will only work for containers that have exactly two template type arguments, so will fail miserably for std::map (thanks Luc Danton).


Any kind of secondary template arguments (not recommended)

The corrected version for any secondary parameter count is as follows:

#include <memory> // for std::allocator<>

template <template <typename, typename...> class Container, 
          typename Value,
          typename... AddParams >
void bar(const Container<Value, AddParams...> & c, const Value & t)
{
  //
}

template <typename T>
class OneParameterVector {};

#include <vector>
int main () {
    OneParameterVector<float> b;
    bar (b, 1.2f);
    std::vector<float> c;
    bar (c, 1.2f);
}

However: this will still fail for non-template containers (thanks Luc Danton).

m.s.
  • 16,063
  • 7
  • 53
  • 88
Sebastian Mach
  • 38,570
  • 8
  • 95
  • 130
  • 2
    What does any of this have to do with `error: 'bar' was not declared in this scope`? Sorry, but OP's code appears to be the correct. I see lots of template magic here, but none of this is how one normally accepts a container in a function template. – UncleBens Oct 11 '11 at 16:14
  • 4
    What happened to generic programming? `template void foo(Container& c) { /* use typename Container::value_type as needed etc. */ ...` – Luc Danton Oct 11 '11 at 16:16
  • 1
    Just to note: ensuring at compile-time that Container::value_type and T are the same (or that Container is a container) is the job for a static assertion. Compare the error diagnostics that you get from innocent mistakes: http://ideone.com/gVVFU (how helpful is that!) and http://ideone.com/ILIGP (with the where and the why). What you see above is only advisable if you are going to provide an overload where those types are not the same, or where the first argument is not a container. – UncleBens Oct 11 '11 at 17:35
  • 1
    @Luc: Nothing happened, and that's still the best solution (see the part on trait checking in my answer). Me, I was tempted by the prospect of having a real sentence published that uses the word "template" four times... – Kerrek SB Oct 11 '11 at 18:25
  • @UncleBens: 0) Question was `So how would I go about implementing my bar function the proper way?`. 1) Define "normally". The standard library _normally_ works on iterator pairs (my fourth proposal). E.g. `std::count`'s signature: `count ( ForwardIterator first, ForwardIterator last, const T& value );`. – Sebastian Mach Oct 13 '11 at 11:58
  • @Luc Danton: That will provoke errors in the function body instead of onto the signature and you will have a harder time forbidding your function for non-containers. My guess is library users will get a better clue by "signature-errors" than by "body-errors". Apart from that, I don't understand your question "What happened to generic programming". – Sebastian Mach Oct 13 '11 at 12:02
  • @UncleBens: Good point on static_assert, but I don't see where my code does not already assert that, e.g. by having the signature say `foo(Container, T)` or `foo(Container, Container::value_type)`. In this respect, you don't even reach the static_assert, because it fails on the signature already. – Sebastian Mach Oct 13 '11 at 12:09
  • @phresnel As is usual in generic programming, you can have custom user messages via e.g. concept checking and/or `static_assert`. Using a binary template template parameters means not being compatible with `std::map`. Which of the two is more generic? (C++11 enables variadic template template parameters but that still restricts against non-template containers.) – Luc Danton Oct 13 '11 at 12:44
  • @Luc Danton: I see what you mean (it is quite the opposite of over-generalization), I'll add that as a remark to the non-traits and non-range version :) Still, your version might be too generic. E.g., if you have two functions `put()`, one for containers, one for scalar types. Then how would your variant not yield non-ambiguous signatures. – Sebastian Mach Oct 13 '11 at 15:16
  • Your code does assert it, but in a way that produces an unhelpful error message. Which is particularly bad in the context of this question: not only did you not address the question, you suggested complications that are bound to produce even more of those cryptic errors for seemingly correct code. - As to suggesting iterator pairs as the "normal" way: if you need the container (for erasing), it's pretty much out the question, right? - I still wonder how a reputable member provides an answer that gives so much questionable and completely off-topic advice. :) – UncleBens Oct 13 '11 at 16:05
  • @UncleBens: Then you must also question the sanity of the asker for accepting a "completely off-topic advice" w.r.t. `c++ template class; function with arbitrary container type, how to define it?` ... ":)". And also answer this question: Assume you have `template foo (Cont, T)`, would you want to introduce an additional template type argument, like `template foo (Cont, U)` just to be able to fall into the body, therefore making it communicate lies ("I can take any type!", user gives any type, "But I won't, fool!"? – Sebastian Mach Oct 13 '11 at 16:20
  • @phresnel SFINAE allows overloading but at the cost of the legible error messages (you can still provide a catch-all that errors out but in the endthat's a lot of work). Anyway those trade-offs are the usual stuff of generic programming. – Luc Danton Oct 13 '11 at 16:33
  • The asker solved their problem themselves, realized that the question was unanswerable, and probably accepted this answer for the effort you put into it. (Also a beginner does not have the experience to evaluate these things.) - Yes, "but I won't, fool!" communicates useful information. "No matching function to call" not so much - then you have to locate the function (in code or manual) to figure it out. - BTW, there is nothing in the question to suggest that the asker is seeking to limit the types of input in this way!! – UncleBens Oct 13 '11 at 16:48
  • @UnceBens: I have a hard time writing incorrect function signatures for the sake of better error messages, and it is in the way of overloading and specialization (what if your really want two functions `f(Cont, T)` and `f(Cont,U)`? Of course you could add an indirection to solve for that, but then the error message becomes even more remote and the situation is more shitty. Better: Learn to read and understand function signatures, and error messages, then you can trivially decrypt `no matching function call XXX, candidates are YYY`. [...] – Sebastian Mach Oct 14 '11 at 08:08
  • @UncleBens: [...] What you want is concepts, I want them, too. But I am not going to befoul signatures and spread lies about acceptance and behaviour. Also, that's not what they call self-documenting. – Sebastian Mach Oct 14 '11 at 08:10
  • @UncleBens: And I am still waiting for an answer for why this is not an answer to _function with arbitrary container type, how to define it?_. My answer was not specific, but generic. Problem? Not want? Okay. That's opinion. Personally, I am happy that ppl tried to give ME generic, re-usable answers. Otherwise I would still be learning things like "how can I output the 5th element of a std::vector<> when the value_type is int and my for-loop is in the third recursion of a function that was called through a function pointer that was const_casted from a reference that was .... which .......... – Sebastian Mach Oct 14 '11 at 08:15
12

Make the template templated on a template template parameter:

template <template <typename, typename...> class Container>
void bar(const Container<T> & c, const T & t)
{
  //
}

If you don't have C++11, then you can't use variadic templates, and you have to provide as many template parameters as your container takes. For example, for a sequence container you might need two:

template <template <typename, typename> class Container, typename Alloc>
void bar(const Container<T, Alloc> & c, const T & t);

Or, if you only want to allow allocators which are themselves template instances:

template <template <typename, typename> class Container, template <typename> class Alloc>
void bar(const Container<T, Alloc<T> > & c, const T & t);

As I suggested in the comments, I would personally prefer to make the entire container a templated type and use traits to check if it's valid. Something like this:

template <typename Container>
typename std::enable_if<std::is_same<typename Container::value_type, T>::value, void>::type
bar(const Container & c, const T & t);

This is more flexible since the container can now be anything that exposes the value_type member type. More sophisticated traits for checking for member functions and iterators can be conceived of; for example, the pretty printer implements a few of those.

Community
  • 1
  • 1
Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • Any rationale why **that** should work. (In general, this should be very unusual thing to do...) – UncleBens Oct 11 '11 at 15:34
  • @UncleBens: What do you mean? You can match all sorts of things with templates. I'm not saying this is the best idea (personally, I'd probably prefer for a trait check of `typename C::value_type`), but hey, it's an option. – Kerrek SB Oct 11 '11 at 15:36
  • But where do you unfold the pack? – Sebastian Mach Oct 11 '11 at 15:42
  • 3
    How does this explain why the original code fails (of course, the question doesn't provide enough information) and why this can be expected to succeed? It is not even clear from the question that T is supposed to be `C::value_type`. - Without this explanation, this sounds like woodoo programming to me. – UncleBens Oct 11 '11 at 15:46
  • I concur with UncleBens. It does not point out the flaws in OP's code (which seems to me to be working), and why wouldn't a traits solution be more elegant, compact and readable? – Captain Giraffe Oct 11 '11 at 15:51
  • 1
    @CaptainGiraffe: Traits are preferable because they allow more general containers. With the template template parameter, you have to anticipate the correct template signature. With a trait check, you can have entirely arbitrary containers, as long as they expose `value_type`. – Kerrek SB Oct 11 '11 at 15:52
  • @UncleBens: Look at the phrases ending with a question mark and you find the question. Maybe stop ranting and start answering? – Sebastian Mach Oct 13 '11 at 12:05
  • @KerrekSB, Could you explain the grammar: `template – thor Jul 05 '14 at 00:02
7

C++20 solution with Concepts and Ranges

In C++20, with the addition of Concepts and Ranges library, we can solve this simply with std::ranges::common_range:

void printContainer(const std::ranges::common_range auto & container);
{
    for(const auto& item : container) std::cout << item;
}

Here, common_range is a concept that all stl containers satisfy. And you can get container's value type with:

std::ranges::range_value_t<decltype(container)>

You can also create your own container type that satisfy the concept with well defined iterator type and it begin() and it end() functions.

  • Alternatively, you can also use std::ranges::range, which has a bit less strict requirement than common_range, so it could allow more custom types.

Attempting to call the function with a non-satisfying type would give you error like template argument deduction/substitution failed: constraints not satisfied.

Ranoiaetep
  • 5,872
  • 1
  • 14
  • 39
  • What is the auto after `std::ranges::common_range` doing? – user3520616 Sep 24 '21 at 23:26
  • 1
    @user3520616 That `auto` is just the same as `auto` in `foo(const auto param);`. It will be deduced to whatever type `param` was. However, putting `common_range` in front of `auto` means only the types that follows the `common_range` concept can be put in there. – Ranoiaetep Sep 24 '21 at 23:33
1

Here's the latest and expanded version of this answer and significant improvement over answer by Sabastian.

The idea is to define all traits of STL containers. Unfortunately, this gets tricky very fast and fortunately lot of people have worked on tuning this code. These traits are reusable so just copy and past below code in file called type_utils.hpp (feel free to change these names):

//put this in type_utils.hpp 
#ifndef commn_utils_type_utils_hpp
#define commn_utils_type_utils_hpp

#include <type_traits>
#include <valarray>

namespace common_utils { namespace type_utils {
    //from: https://raw.githubusercontent.com/louisdx/cxx-prettyprint/master/prettyprint.hpp
    //also see https://gist.github.com/louisdx/1076849
    namespace detail
    {
        // SFINAE type trait to detect whether T::const_iterator exists.

        struct sfinae_base
        {
            using yes = char;
            using no  = yes[2];
        };

        template <typename T>
        struct has_const_iterator : private sfinae_base
        {
        private:
            template <typename C> static yes & test(typename C::const_iterator*);
            template <typename C> static no  & test(...);
        public:
            static const bool value = sizeof(test<T>(nullptr)) == sizeof(yes);
            using type =  T;

            void dummy(); //for GCC to supress -Wctor-dtor-privacy
        };

        template <typename T>
        struct has_begin_end : private sfinae_base
        {
        private:
            template <typename C>
            static yes & f(typename std::enable_if<
                std::is_same<decltype(static_cast<typename C::const_iterator(C::*)() const>(&C::begin)),
                             typename C::const_iterator(C::*)() const>::value>::type *);

            template <typename C> static no & f(...);

            template <typename C>
            static yes & g(typename std::enable_if<
                std::is_same<decltype(static_cast<typename C::const_iterator(C::*)() const>(&C::end)),
                             typename C::const_iterator(C::*)() const>::value, void>::type*);

            template <typename C> static no & g(...);

        public:
            static bool const beg_value = sizeof(f<T>(nullptr)) == sizeof(yes);
            static bool const end_value = sizeof(g<T>(nullptr)) == sizeof(yes);

            void dummy(); //for GCC to supress -Wctor-dtor-privacy
        };

    }  // namespace detail

    // Basic is_container template; specialize to derive from std::true_type for all desired container types

    template <typename T>
    struct is_container : public std::integral_constant<bool,
                                                        detail::has_const_iterator<T>::value &&
                                                        detail::has_begin_end<T>::beg_value  &&
                                                        detail::has_begin_end<T>::end_value> { };

    template <typename T, std::size_t N>
    struct is_container<T[N]> : std::true_type { };

    template <std::size_t N>
    struct is_container<char[N]> : std::false_type { };

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

    template <typename T1, typename T2>
    struct is_container<std::pair<T1, T2>> : std::true_type { };

    template <typename ...Args>
    struct is_container<std::tuple<Args...>> : std::true_type { };

}}  //namespace
#endif

Now you can use these traits to make sure our code only accepts container types. For example, you can implement append function that appends one vector to another like this:

#include "type_utils.hpp"

template<typename Container>
static typename std::enable_if<type_utils::is_container<Container>::value, void>::type
append(Container& to, const Container& from)
{
    using std::begin;
    using std::end;
    to.insert(end(to), begin(from), end(from));
}

Notice that I'm using begin() and end() from std namespace just to be sure we have iterator behavior. For more explanation see my blog post.

Community
  • 1
  • 1
Shital Shah
  • 63,284
  • 17
  • 238
  • 185