1

I have a series of templated classes that I'd like to make aware of each other at compile-time. Each object may have other compile-time attributes that would be used to setup runtime conditions for the code.

My ideal pattern would be something like this:

template <unsigned int A, unsigned int B>
class FirstClass
{
};

template < template<unsigned int A, unsigned int B> FirstClass &firstClass >
class SecondClass
{
};

//...

FirstClass<1,2> fc;
SecondClass<fc> sc;
ThirdClass<sc> tc;
//...

Is there a way to do this?

I can get close if I do something like this for SecondClass:

template < unsigned int A, unsigned int B, FirstClass<A,B> &firstClass >

But this then requires me to pass the two extra arguments (rather than have the compiler infer them) and will not scale very well.

Thanks!

spease
  • 686
  • 6
  • 15
  • What are you trying to accomplish with this machinery? What do you think about just having a "traits" class that can be passed as the template argument for both FirstClass and SecondClass? – John Zwinck Mar 15 '14 at 03:30
  • @JohnZwinck He still won't be able to pass an instance(reference) as a template parameter though. – Eric Fortin Mar 15 '14 at 03:43

2 Answers2

0

I think C++11's decltype is what you're looking for. It will let you abstain from writing those template arguments over and over again.

It's important to note that in the example below, the pointer code in SecondClass is entirely unnecessary and I only included it because I wasn't sure whether your project needed runtime access. ThirdClass is the preferred example.

EDIT: I read your comment about arbitrary number of types. FourthClass or FifthClass here may be what you're looking for. It uses variadic templates, tuples, and some TMP code (for_each iterating over a tuple) from https://stackoverflow.com/users/680359/emsr in question iterate over tuple.

I hope there is enough here to get you started.

#include<iostream>
#include<tuple>
#include<string>

template <unsigned int A, unsigned int B>
struct FirstClass
{
    static constexpr unsigned int C = A;
    static constexpr unsigned int D = B;
};

template < typename T, const T* const t >
struct SecondClass
{

    static constexpr unsigned int FOR_THIRD_CLASS = T::C;

    //SecondClass knows about a FirstClass instance at compile time
    static constexpr T* const pFirstClass = t;

    //uses FirstClass values, which were computed at compile time, at runtime
    void printFirstClassValues() const {

        //ThirdClass below is an example without pointers or references, which it sounds like you don't need
        std::cout << t -> C << " " << t -> D;
    }

};

template < typename T >
struct ThirdClass
{
    void printSecondClassValue() const {
        std::cout << "\nIn ThirdClass method: " << T::FOR_THIRD_CLASS;
    }
};


static constexpr FirstClass<1,2> fc;

template<std::size_t I = 0, typename FuncT, typename... Tp>
inline typename std::enable_if<I == sizeof...(Tp), void>::type
  for_each(std::tuple<Tp...> &, FuncT) // Unused arguments are given no names.
  { }

template<std::size_t I = 0, typename FuncT, typename... Tp>
inline typename std::enable_if<I < sizeof...(Tp), void>::type
  for_each(std::tuple<Tp...>& t, FuncT f)
  {
    f(std::get<I>(t));
    for_each<I + 1, FuncT, Tp...>(t, f);
  }

struct Functor 
{
    template<typename T>
    void operator()(T& t) const { std::cout << t << ", "; }
};

template< typename... Ts >
struct FourthClass{

    std::tuple< Ts... > myTuple;

    //if you need it...
    static constexpr int numberOfTypes = sizeof...(Ts);

    FourthClass(Ts... pack):myTuple(pack...){
    }

    void print(){
        for_each( myTuple, Functor() );
    }

};

//maybe this is better - give it a tuple to begin with
template < typename my_tuple >
class FifthClass{

};

//just use your imagination here - these are ridiculous typedefs that don't ever make sense to use, I'm just showing you how to use FifthClass with a variable number of types
typedef SecondClass< decltype(fc), &fc > SC;
typedef ThirdClass<SC> TC;
typedef FourthClass<TC> FC;
typedef std::tuple<SC,TC,FC> ArbitraryClasses;
typedef std::tuple<SC,TC,FC,ArbitraryClasses> OtherArbitraryClasses;
typedef std::tuple<SC,TC,FC,ArbitraryClasses,OtherArbitraryClasses, int, std::string> MoreArbitraryClasses;

int main(){

    SecondClass<decltype(fc), &fc> sc;
    ThirdClass<decltype(sc)> tc;

    sc.printFirstClassValues();
    tc.printSecondClassValue();


    std::cout << "\nEdit: here's a variadic example..." << std::endl;

    FourthClass < int,unsigned int, short, const char*, int*, std::string > fourth(9,6,19,"this is a string", (int*)0xDEADBEEF, "I could keep going with any cout-able types");
    fourth.print();

    FifthClass < MoreArbitraryClasses > fifth;  

    return 0;
}
Community
  • 1
  • 1
Barrett Adair
  • 1,306
  • 10
  • 24
  • This seems like it's in the right direction. The major difference I can see is that I'd like the changes to accumulate. I guess I didn't make it clear in my original post, but I'd like it to work with an arbitrary number of classes - known at compile-time, but which ones are used may vary with the implementation. For instance, one program might use First, Second, and Fourth classes; another might use First, Fifth, Seventh, and Ninth. – spease Mar 20 '14 at 06:52
  • What do you mean? T can be anything as long as the member names match usage in the template. If you want an arbitrary number of classes to be passed to a single template, you can make it a variadic template. If you need runtime access, you can use references or pointers (which can also be variadic parameters, meaning you can have an arbitrary number of objects to access at runtime. Can you explain a bit more clearly (known at compile-time by what? Does that thing need to know an arbitrary number of classes at the same time? )? – Barrett Adair Mar 20 '14 at 08:30
  • I went ahead and gave some examples in FourthClass and FifthClass of ways to have an arbitrary number of types in a template. – Barrett Adair Mar 20 '14 at 11:02
0

The right question is: do you really care if the argument of the second template is really from the first, or is it ok with you if it behaves exactly like the first template?

In the second case, there is really nothing to do. Simply use normal template argument.

In the first case, you can always use static_assert with is_same. It would require the first type to have constants for the two arguments:

template <unsigned int A, unsigned int B>
class FirstClass
{
public:
  constexpr static unsigned int a = A;
  constexpr static unsigned int b = B;
};

Then you can do:

template <typename FC>
class SecondClass
{
  static_assert(std::is_same<FC,FirstClass<FC::a, FC::b> >::value, "Wrong template argument for SecondClass");
};

If you are not using C++11, look at the static_assert implementation in Boost, it is not that complex. And you will also have to implement yourself the is_same, but I don't know that is difficult.

And to use it:

FirstClass<1,2> fc;
SecondClass<decltype(fc)> sc;

Note that you will never be allowed to use a local variable at template argument.

Another thing you might want to look at (still C++11) is a helper function:

If you re-write your second class as:

template <unsigned int A, unsigned int B>
struct SecondClass
{
    FirstClass<A,B> fc;

    A(FirstClass<A,B> arg)
       :fc(arg)
    { }
};

Then you can write:

template <unsigned int A, unsigned int B>
SecondClass<A,B> secondClass(FirstClass<A,B> arg)
{
  return SecondClass<A,B>(arg);
}

And in you function:

FirstClass<1,2> fc;
auto sc = secondClass(fc)
PierreBdR
  • 42,120
  • 10
  • 46
  • 62