4

Please don't get my "late binding" wrong, I don't mean usual late binding at runtime, I mean something else and cannot find a better word for it:

Consider I am working on a container (or similar) data structure Containor for some value type V that needs to compare these values with a comparator, so my first template looks like this

template<typename Val, typename Comp = std::less<Val>>
struct Containor{};

Now, my Containor structure makes use of another container internally. Which container is to be used should be configurable by template arguments as well, lets say the default is std::set. So my next version of Containor looks like this:

template<typename Val, typename Comp = std::less<Val>, typename Cont = std::set<Val,Comp>>
struct Containor{};

and here is where the code begins smelling IMHO. As long as the user is satisfied with the default implementation of the inner container, everything is fine. However, suppose he wants to use the new google btree set implementation btree::btree_set instead of std::set. Then he has to instanciate the template like this:

typedef Containor<int,std::less<int>,btree::btree_set<int,std::less<int>> MyContainor;
                                                     ^^^^^^^^^^^^^^^^^^^

I have underlined the part where my problem lies. The CLIENT CODE has to instanciate the btree_set with the right parameters. This honestly sucks, because the Containor class always needs a set of exactly the same type and comparator as its own first two template arguments. The client can - by accident - insert other types here! In addition, the client has the burden of choosing the right parameters. This might be easy in this case, but it hard if the inner container must for example be a set of pairs of the value type and some other type. Then the client has an even harder time getting the type parameters of the inner set correct.

So what I want is a way in which the client code only hands in the raw template and the Containor internally instanciates it with the correct arguments, i.e. something like that:

template<typename Val, typename Comp = std::less<Val>, typename Cont = std::set >
struct Containor{
    typedef Cont<Val,Comp> innerSet; 
//  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ container instanciates the inner containor
};

typedef Containor<int,std::less<int>,btree::btree_set> MyContainor;
//                                   ^^^^^^^^^^^^^^^^
//                         client only hands in  raw template

Of course, this is no valid C++!

So I thought about ways to solve this problem. The only solution I could think of was writing "binder classes" for all data structures I want to use, like this:

struct btree_set_binder{

    template<typename V, typename C = std::less<V>>
    struct bind{
        typedef btree::btree_set<V,C> type;
    }
};

Now I can define my Containor with a set binder

template<typename Val, typename Comp = std::less<Val>, typename ContBinder = btree_set_binder >
struct Containor{
    typedef btree_set_binder::bind<Val,Comp>::type innerSet; 
//          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ works like a charm
};

Now, the user must only supply the desired binder class and the Containor will instanciate it with the right arguments. So these binder classes would be okay for me, but it is quite a hassle writing binder classes for all containers. So is there a better or easier way to bind template arguments "late" in C++11, i.e., inside another template that retrieves the raw template as parameter.

gexicide
  • 38,535
  • 21
  • 92
  • 152
  • Does your inner container always require a predicate? Then just extract that. – Kerrek SB Feb 22 '13 at 17:13
  • 3
    I guess what you need is *template template parameters* – Andy Prowl Feb 22 '13 at 17:13
  • @Kerrek SB: I don't get what you mean. Can you elaborate on that a bit more. – gexicide Feb 22 '13 at 17:13
  • 1
    @gexicide: well, use `Cont::key_compare` for the comparator... Or if you want to be even more flexible, make a trait. In fact, I'll post that as an answer. – Kerrek SB Feb 22 '13 at 17:14
  • @Andy Prowl: Wow, didn't know that existed. That was exactly what I needed! – gexicide Feb 22 '13 at 17:18
  • 2
    @gexicide: template templates exist, but you should generally avoid them for situations like yours, since you can never tell what a client template might actually look like. It's better for a template to expose its own parameters and use traits. – Kerrek SB Feb 22 '13 at 17:20
  • @gexicide: I deleted my answer, because I realized `btree::tree_set` accepts 4 arguments, so it would not be compatible. I think KerrekSB has the best solution. – Andy Prowl Feb 22 '13 at 17:38
  • @KerrekSB: I would still be interested in knowing where does the Standard specify that `std::set` could have any number of template parameters – Andy Prowl Feb 22 '13 at 17:39
  • @AndyProwl: I think it's an urban legend (potentially fuelled by MSVC's implementation). Someone once told me off for this for my pretty printer code. Thanks to [this fine post](http://stackoverflow.com/q/1469743/596781) I'm now certain that in fact there is no such provision. Apologies! – Kerrek SB Feb 22 '13 at 17:46
  • @KerrekSB: That's fine, thank you for clarifying. – Andy Prowl Feb 22 '13 at 17:47

3 Answers3

4

Maybe make your own comparator trait.

// Comparator trait primary template

template <typename T> stuct MyComparator
{
    typedef typename T::key_compare type;
};

// Comparator trait non-standard usage example

template <typename U, typename V, int N>
struct MyComparator<WeirdContainer<U, V, N>>
{
    typedef std::greater<V> type;
};

template <typename T, typename Cont = std::set<T>>
struct MyAdaptor
{
    typedef typename MyComparator<Cont>::type comparator_type;
    typedef T value_type;

    // ...
};

I've renamed your "Containor" to "MyAdaptor", since this sort of construction is usually called an "adaptor" class.

Usage:

MyAdaptor<int> a;    // uses std::set<int> and std::less<int>

MyAdaptor<double, WeirdContainer<bool, double, 27>> b;

Update: In light of the discussion, you could even remove the outer type argument entirely:

template <typename Cont> struct MyBetterAdaptor
{
    typedef MyAdaptorTraits<Cont>::value_type value_type;
    typedef MyAdaptorTraits<Cont>::pred_type pred_type;

    // ...
};

To be used like this:

MyBetterAdaptor<std::set<int>> c; // value type "int", predicate "std::less<int>"

Writing the MyAdaptorTraits template is left as an exercise.

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • still, how do you instanciate `MyAdaptor` with another `Cont` without specifying template arguments? – gexicide Feb 22 '13 at 17:19
  • @Edit: But that doesn't solve my problem. Again, the user must supply all template arguments to `WeirdContainer`. when instanciating `MyAdaptor>`. Exactly these arguments were what I wanted to get rid of. – gexicide Feb 22 '13 at 17:21
  • @gexicide: You're going to have to repeat the value type, but that's an OK price to pay, since it's the most general design you can get. The standard library is constructed in the same way, and for a reason. – Kerrek SB Feb 22 '13 at 17:21
  • @gexicide: Well, we do avoid having to specify the *predicate* repeatedly, right? That's a big improvement. I would consider this design perfectly acceptable. Again, compare with the standard library -- even *they* thought this was good library design. – Kerrek SB Feb 22 '13 at 17:23
  • but see my post. What if the inner container may not of the value type but of a quite complex type like `std::tuple`. Do you want to shift the burden of getting this type correct to the user? – gexicide Feb 22 '13 at 17:23
  • @gexicide: In that case I don't even see an alternative! If the inner container can potentially be unrelated to your outer value type, then how are *you* going to deduce the right type? The user is the *only* person who can specify that. – Kerrek SB Feb 22 '13 at 17:24
  • No, you are misunderstanding me :). Consider the container requires the client to use a X> as inner container. It must always be exactly this signature. The container is heavily related to the value type, but not in such a subtle way as in my example. Now, each client would have to write `MyAdaptor>`. Isn't this super ugly? The client must get the parameters right although they are out of his scope since they must ALWAYS be std::tuple. If V is a long type, then the whole typedef becomes huge. – gexicide Feb 22 '13 at 17:29
  • In that case just remove the outer type as well and put it in the trait! (Updated the post.) – Kerrek SB Feb 22 '13 at 17:33
  • but isn't the trait is again client supplied (assuming that he does not want to use the default trait)? Again, the client has to get the template parameters of the inner type correct somewhere (in the trait instead of in the declaration of the type). In my real code, the inner type is even more ugly than just a tuple. Especially if the user instanciates `V` with a long type. The resulting type is just so super long and ugly that I never want to require a client to write out this type. – gexicide Feb 22 '13 at 17:37
  • @gexicide: Any sane class would expose the type, such as `std::set::key_type`. The primary trait template would just default to that, so for sane clients no extra code is needed. If your client template is not being decent, you have to supply a trait specialization (and file a bug with the client author). Note how the first example (`a`) makes no mention of `std::less` either, although that's the predicate. – Kerrek SB Feb 22 '13 at 17:48
  • Okay, then what about this use case (that I also have): Suppose class `Containor` uses two data structure types `Cont1` and `Cont2`. They might be the same type, but they do not have to. For performance reasons,e.g., it might be better to use another implementation for the first than for the second. However `Containor` must ensure that both get instanciated with the same template arguments,e.g.,`pair`. How can I do this with traits? Of course I can use static_assert, but again the client has the burden to use the right parameters twice (once for both Comps). – gexicide Feb 22 '13 at 18:11
  • @gexicide: Add the static assert and live with it. No extra level of machinery could make that situation easier for the user. – Kerrek SB Feb 22 '13 at 18:40
2

So what I want is a way in which the client code only hands in the raw template and the Containor internally instanciates it with the correct arguments,

So what you need is obviously a template template parameter:

// std::set has three template parameters, 
// we only want to expose two of them ...
template <typename V, typename C>
using set_with_defalloc = std::set<V,C>;

template<
    typename Val,
    typename Comp = std::less<Val>, 
    template <typename V, typename C> class Cont = set_with_defalloc>
struct Containor{
    typedef Cont<Val,Comp> innerSet; 
    // ...
};
JoergB
  • 4,383
  • 21
  • 19
1

You should be able to do it with template template parameters, as in:

template<typename Val, typename Comp = std::less<Val>, template <typename...> class ContBinder = std::set>
    struct Containor {
        typedef ContBinder<Val, Comp> innerSet;
        // ...
    };

Note: you need the variadic typename... because std::set takes three template parameters (the third being an allocator) while other containers might not.

Nevin
  • 4,595
  • 18
  • 24