6

I am reluctant to say I can't figure this out, but I can't figure this out. I've googled and searched Stack Overflow, and come up empty.

The abstract, and possibly overly vague form of the question is, how can I use the traits-pattern to instantiate member functions? [Update: I used the wrong term here. It should be "policies" rather than "traits." Traits describe existing classes. Policies prescribe synthetic classes.] The question came up while modernizing a set of multivariate function optimizers that I wrote more than 10 years ago.

The optimizers all operate by selecting a straight-line path through the parameter space away from the current best point (the "update"), then finding a better point on that line (the "line search"), then testing for the "done" condition, and if not done, iterating.

There are different methods for doing the update, the line-search, and conceivably for the done test, and other things. Mix and match. Different update formulae require different state-variable data. For example, the LMQN update requires a vector, and the BFGS update requires a matrix. If evaluating gradients is cheap, the line-search should do so. If not, it should use function evaluations only. Some methods require more accurate line-searches than others. Those are just some examples.

The original version instantiates several of the combinations by means of virtual functions. Some traits are selected by setting mode bits that are tested at runtime. Yuck. It would be trivial to define the traits with #define's and the member functions with #ifdef's and macros. But that's so twenty years ago. It bugs me that I cannot figure out a whiz-bang modern way.

If there were only one trait that varied, I could use the curiously recurring template pattern. But I see no way to extend that to arbitrary combinations of traits.

I tried doing it using boost::enable_if, etc.. The specialized state information was easy. I managed to get the functions done, but only by resorting to non-friend external functions that have the this-pointer as a parameter. I never even figured out how to make the functions friends, much less member functions. The compiler (VC++ 2008) always complained that things didn't match. I would yell, "SFINAE, you moron!" but the moron is probably me.

Perhaps tag-dispatch is the key. I haven't gotten very deeply into that.

Surely it's possible, right? If so, what is best practice?

UPDATE: Here's another try at explaining it. I want the user to be able to fill out an order (manifest) for a custom optimizer, something like ordering off of a Chinese menu - one from column A, one from column B, etc.. Waiter, from column A (updaters), I'll have the BFGS update with Cholesky-decompositon sauce. From column B (line-searchers), I'll have the cubic interpolation line-search with an eta of 0.4 and a rho of 1e-4, please. Etc...

UPDATE: Okay, okay. Here's the playing-around that I've done. I offer it reluctantly, because I suspect it's a completely wrong-headed approach. It runs okay under vc++ 2008.

#include <boost/utility.hpp>
#include <boost/type_traits/integral_constant.hpp>

namespace dj {

struct CBFGS {
    void bar() {printf("CBFGS::bar %d\n", data);}
    CBFGS(): data(1234){}
    int data;
};

template<class T>
struct is_CBFGS: boost::false_type{};

template<>
struct is_CBFGS<CBFGS>: boost::true_type{};

struct LMQN {LMQN(): data(54.321){}
    void bar() {printf("LMQN::bar %lf\n", data);}
    double data;
};

template<class T>
struct is_LMQN: boost::false_type{};

template<>
struct is_LMQN<LMQN> : boost::true_type{};

// "Order form"
struct default_optimizer_traits {
    typedef CBFGS update_type; // Selection from column A - updaters
};

template<class traits> class Optimizer;

template<class traits>
void foo(typename boost::enable_if<is_LMQN<typename traits::update_type>, 
         Optimizer<traits> >::type& self) 
{
    printf(" LMQN %lf\n", self.data);
}

template<class traits>
void foo(typename boost::enable_if<is_CBFGS<typename traits::update_type>,  
         Optimizer<traits> >::type& self) 
{
    printf("CBFGS %d\n", self.data);
}

template<class traits = default_optimizer_traits>
class Optimizer{
    friend typename traits::update_type;
    //friend void dj::foo<traits>(typename Optimizer<traits> & self); // How?
public:
    //void foo(void); // How???
    void foo() {
        dj::foo<traits>(*this);
    }
    void bar() {
        data.bar();
    }
//protected: // How?
    typedef typename traits::update_type update_type;
    update_type data;
};

} // namespace dj



int main() {
    dj::Optimizer<> opt;
    opt.foo();
    opt.bar();
    std::getchar();
    return 0;
}
Jive Dadson
  • 16,680
  • 9
  • 52
  • 65
  • 2
    Can you show an example of what you're doing now in pseudo code, perhaps? – Chris K Apr 14 '10 at 22:13
  • @Chris Kaminski: The 10 year old code, or the experiments? The latter, I presume. – Jive Dadson Apr 14 '10 at 22:36
  • Both perhaps? I mean, there's lots of examples in the STL and boost about using functions operating on collections, map/reduce for example. – Chris K Apr 14 '10 at 23:08
  • I'm confused by "instantiate non-virtual member function". – Edward Strange Apr 14 '10 at 23:20
  • "Instantiate" is what the compiler does with a template when the template is invoked. The compiler makes a concrete 'instance' of the abstraction that the template represents. – Jive Dadson Apr 14 '10 at 23:47
  • @Chris Kaminski: I easily managed to use boost::enable_if to instantiate external functions that can operate on the optimizer base class, provided that I make all the protected parts of the base class public. What I have not figured out how to do is to instantiate member functions, or even to declare the external functions as friend. – Jive Dadson Apr 14 '10 at 23:53
  • @jive - ok. I know that but "non-virtual member function" doesn't say "template member function" to me. Interesting problem, when I get a chance I'll have to check out the answers and see if I can learn or do better. – Edward Strange Apr 15 '10 at 16:21
  • @Eddy Pronk - I'm not thrilled by any of the answers, including my own. It's my fault for asking such a vague question. But if it's important to you, you got it. – Jive Dadson Apr 20 '10 at 15:28

5 Answers5

2

A simple solution might be to just use tag-based forwarding, e.g. something like this:

template<class traits>
void foo(Optimizer<traits>& self, const LMQN&) {
    printf(" LMQN %lf\n", self.data.data);
}

template<class traits>
void foo(Optimizer<traits>& self, const CBFGS&) {
    printf("CBFGS %d\n", self.data.data);
}

template<class traits = default_optimizer_traits>
class Optimizer {
    friend class traits::update_type;
    friend void dj::foo<traits>(Optimizer<traits>& self, 
                            const typename traits::update_type&);
public:
    void foo() {
        dj::foo<traits>(*this, typename traits::update_type());
    }
    void bar() {
        data.bar();
    }
protected:
    typedef typename traits::update_type update_type;
    update_type data;
};

Or if you want to conveniently group several functions together for different traits, maybe something like this:

template<class traits, class updater=typename traits::update_type> 
struct OptimizerImpl;

template<class traits>
struct OptimizerImpl<traits, LMQN> {
    static void foo(Optimizer<traits>& self) {
        printf(" LMQN %lf\n", self.data.data);
    }
};

template<class traits> 
struct OptimizerImpl<traits, CBFGS> {
    static void foo(Optimizer<traits>& self) {
        printf("CBFGS %d\n", self.data.data);
    }
};

template<class traits = default_optimizer_traits>
class Optimizer{
    friend class traits::update_type;
    friend struct OptimizerImpl<traits>;
public:
    void foo() {
        OptimizerImpl<traits>::foo(*this);
    }
    // ...
};
Georg Fritzsche
  • 97,545
  • 26
  • 194
  • 236
  • That's what I was going to write somehow. I've been focused on the use of `enable_if` when the problem at hand certainly does not require it. A simple `Strategy` pattern or `Policy`-based design seems much more suitable. – Matthieu M. Apr 15 '10 at 15:45
  • I am sort of on that same track. The idea came to me as I slept. That happens to me a lot. My idea is, since the member functions cannot be specialized unless the containing class is specifically specialized, I'll have a template that splits the manifest into its component parts then invokes another template that has them separated like what you show. I have dogs and the IRS to mollify today. But I'll get around to it. – Jive Dadson Apr 15 '10 at 17:12
  • @Jive: Let us know how it goes. From what you have shown, i don't see the necessity to insert another template layer though - but then you might have something else in mind. – Georg Fritzsche Apr 16 '10 at 13:36
1

I think template specialization is a step in the right direction. This doesn't work with functions so I switched to classes. I changed it so it modifies the data. I'm not so sold on the protected members and making friends. Protected members without inheritance is a smell. Make it public or provide accessors and make it private.

template <typename>
struct foo;

template <>
struct foo<LMQN>
{
    template <typename OptimizerType>
    void func(OptimizerType& that)
    {
        printf(" LMQN %lf\n", that.data.data);
        that.data.data = 3.14;
    }
};

template <>
struct foo<CBFGS>
{
    template <typename OptimizerType>
    void func(OptimizerType& that)
    {
        printf(" CBFGS %lf\n", that.data.data);
    }
};

template<class traits = default_optimizer_traits>
class Optimizer{
public:
    typedef typename traits::update_type update_type;
    void foo() {
        dj::foo<typename traits::update_type>().func(*this);
    }
    void bar() {
        data.bar();
    }
    update_type data;
};
Eddy Pronk
  • 6,527
  • 5
  • 33
  • 57
  • There's no way for the updater foo to update the protected members of class Optimizer or to read its state. The function to be instantiated should be a member of class Optimizer, or failing that, a friend that gets passed the this-pointer of the Optimizer as an argument. Look at the try I made at it. The functions work, but only if I make everything in class Optimizer public, because I have not figured out how to make declare the functions as friends. – Jive Dadson Apr 15 '10 at 03:11
  • (Commenting again because Stackoverflow won't let me edit the previous one.) I think you're on the right track with explicit specialization. But this example misses the point. There's no way for the updater foo to update the protected members of class Optimizer or to read its state. [... as before]. – Jive Dadson Apr 15 '10 at 03:31
  • It looks like this approach is a dead-end. In the accepted answer here, http://stackoverflow.com/questions/2097811/c-syntax-for-explicit-specialization-of-a-template-function-in-a-template-class it says, "You can't specialize a member function without explicitly specializing the containing class." So we get into the "multiple dispatch" problem with the Chinese menu. But there is hope. It goes on to say, "What you can do however is forward calls to a member function of a partially specialized type." I'll be looking into that. (#defines, #ifdef's, and macros are looking better and better. :-)) – Jive Dadson Apr 15 '10 at 04:34
  • I see you modified the code. Now you're to the point where I was stumped. How to declare the function as a friend? But you have it wrapped in a class, and I can declare the class as a friend. So this is close. I really want to make the function a proper member. But see the comment above. I just want to tell the compiler, if you see "CBFGS" in the traits struct, cut and paste this code for the member function "foo" (or better, "update"). It's frustrating that something that would be so simple with the pre-processor is mind-boggling with templates. But I can't give up on a puzzle. – Jive Dadson Apr 15 '10 at 04:45
1

It would be trivial to define the traits with #define's and the member functions with #ifdef's and macros. But that's so twenty years ago.

Although it may be worth learning new methods, macros are often the simplest way to do things and shouldn't be discarded as a tool just because they're "old". If you look at the MPL in boost and the book on TMP you'll find much use of the preprocessor.

Edward Strange
  • 40,307
  • 7
  • 73
  • 125
  • I'm just considering it to be a puzzle at this point. I am definitely old school. I have no allergic reaction to pre-processor macros. Ironically, I campaigned for some of the very template features that I am struggling with. That had no effect though. Bjarne agreed about the features, but said at that point he had turned it over to the committee and was pretty much staying out of it. – Jive Dadson Apr 15 '10 at 17:21
0

Your use of enable_if is somewhat strange. I've seen it used it only 2 ways:

  • in place of the return type
  • as a supplementary parameter (defaulted)

Using it for a real parameter might cause the havoc.

Anyway, it's definitely possible to use it for member functions:

template<class traits = default_optimizer_traits>
class Optimizer{
  typedef typename traits::update_type update_type;
public:

  typename boost::enable_if< is_LQMN<update_type> >::type
  foo()
  {
    // printf is unsafe, prefer the C++ way ;)
    std::cout << "LQMN: " << data << std::endl;
  }

  typename boost::enable_if< is_CBFGS<update_type> >::type
  foo()
  {
    std::cout << "CBFGS: " << data << std::endl;
  }


private:
  update_type data;
};

Note that by default enable_if returns void, which is eminently suitable as a return type in most cases. The "parameter" syntax is normally reserved for the constructor cases, because you don't have a return type at your disposal then, but in general prefer to use the return type so that it does not meddle with overload resolution.

EDIT:

The previous solution does not work, as noted in the comments. I could not find any alternative using enable_if, only the "simple" overload way:

namespace detail
{
  void foo_impl(const LMQN& data)
  {
    std::cout << "LMQN: " << data.data << std::endl;
  }

  void foo_impl(const CBFGS& data)
  {
    std::cout << "CBFGS: " << data.data << std::endl;
  }
} // namespace detail

template<class traits = default_optimizer_traits>
class Optimizer{
  typedef typename traits::update_type update_type;

public:
  void foo() { detail::foo_impl(data); }

private:
  update_type data;
};

It's not enable_if but it does the job without exposing Optimizer internals to everyone. KISS ?

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • 1
    Are you quite sure that the code works? From similar code I'm getting real errors, not substitution failures (which are not errors only in function templates). – UncleBens Apr 15 '10 at 07:20
  • I think I understand: we are not testing the template parameters proper to the methods, but instead the template parameter is fixed as we instantiate the class which will play havoc with SFINAE I guess. I'll have to try and come up with a better alternative then. That's what happens when you don't compile I guess. – Matthieu M. Apr 15 '10 at 14:11
  • Haha, time has passed mate! You still need to fix it xD – Johannes Schaub - litb Apr 26 '10 at 20:41
  • The only alternative I found was not to use templates :/ I'll add it anyway. – Matthieu M. Apr 27 '10 at 11:15
0

Here's what I (the OP) came up with. Can you make it cooler?

The main Optimizer template class inherits policy-implementation classes. It gives those classes access to the Optimizer's protected members that they require. Another Optimizer template class splits the manifest into its constituent parts and instantiates the main Optimizer template.

#include <iostream>
#include <cstdio>

using std::cout;
using std::endl;

namespace dj {

// An updater.
struct CBFGS {
    CBFGS(int &protect_)
        : protect(protect_)
    {}

    void update () {
        cout << "CBFGS " << protect << endl;
    }   

    // Peek at optimizer's protected data
    int &protect;

};

// Another updater
struct LMQN {
    LMQN(int &protect_)
        : protect(protect_)
    {}

    void update () {
        cout << "LMQN " << protect << endl;
    }

    // Peek at optimizer's protected data
    int &protect;

};

// A line-searcher
struct cubic_line_search {
    cubic_line_search (int &protect2_)
        : protect2(protect2_)
    {}

    void line_search() {
        cout << "cubic_line_search  " << protect2 << endl;
    }   

    // Peek at optimizer's protected data
    int &protect2;

};

struct default_search_policies {
    typedef CBFGS update_type;
    typedef cubic_line_search line_search_type;
};

template<class Update, class LineSearch>
class Opt_base: Update, LineSearch
{
public:
    Opt_base()
        : protect(987654321) 
        , protect2(123456789)
        , Update(protect)
        , LineSearch(protect2)
    {}
    void minimize() {
        update();
        line_search();
    }

protected:
    int protect;
    int protect2;
};

template<class Search_Policies=default_search_policies>
class Optimizer: 
    public Opt_base<typename Search_Policies::update_type
                  , typename Search_Policies::line_search_type
                    >
{};

} // namespace dj



int main() {
    dj::Optimizer<> opt; // Use default search policies
    opt.minimize();

    struct my_search_policies {
        typedef dj::LMQN update_type;
        typedef dj::cubic_line_search line_search_type;
    };

    dj::Optimizer<my_search_policies> opt2;
    opt2.minimize();

    std::getchar();
    return 0;
}
Jive Dadson
  • 16,680
  • 9
  • 52
  • 65