Reputation: 711
We are trying to refactor our code and one of the improvements we want is the following: many functions have many arguments but many of them share a common subset. So, we would like to create a structure that would group them. The problem is that some functions need some of the parameters to be const and some not. Some of these functions must be able to call a subset of these functions supplying this parameter-grouping structure, but under the following restriction: a called function cannot "degrade" the constness of this structure (see the following example). Implementing all required variations of this struct solves the problem but not elegantly. One solution we're working on is to use templates, e.g:
template<class A, class B, class C>
struct my_container
{
A a;
B b;
C c;
};
void foo1(my_container<int, char, float const> & my_cont)
{
}
void foo2(my_container<int const, char, float const> & my_cont)
{
// This should NOT be allowed: we do mind something being const to be treated by the
// called function as non-const.
foo1(my_cont);
}
void foo3(my_container<int, char, float> & my_cont)
{
// This should be allowed: we don't mind something being non-const to be treated by the
// called function as const.
foo2(my_cont);
}
Our problem is that foo2 calls foo1 without the compiler complaining, and we'd like the exact opposite. Is this even possible to implement with templates? Is there any other technique?
Upvotes: 2
Views: 180
Reputation: 2842
My solution with a bit meta-programming. Looks pretty ugly and not throughoutly tested, but anyways: This way, everything should work in general "out of the box"
#include <iostream>
#include <boost/type_traits.hpp>
template<class A, class B, class C>
struct my_container
{
A a;
B b;
C c;
template<typename An, typename Bn, typename Cn>
operator my_container<An,Bn,Cn>& ()
{
/* First, check whether compatible at all */
BOOST_STATIC_ASSERT((boost::is_same<typename boost::remove_cv<A>::type, typename boost::remove_cv<An>::type>::value));
BOOST_STATIC_ASSERT((boost::is_same<typename boost::remove_cv<B>::type, typename boost::remove_cv<Bn>::type>::value));
BOOST_STATIC_ASSERT((boost::is_same<typename boost::remove_cv<C>::type, typename boost::remove_cv<Cn>::type>::value));
/* Enforce const'ness */
BOOST_STATIC_ASSERT( !boost::is_const<A>::value || boost::is_const<An>::value );
BOOST_STATIC_ASSERT( !boost::is_const<B>::value || boost::is_const<Bn>::value );
BOOST_STATIC_ASSERT( !boost::is_const<C>::value || boost::is_const<Cn>::value );
return *reinterpret_cast< my_container<An,Bn,Cn>* >(this);
}
};
void foo1(my_container<int, char, float const> & my_cont)
{
}
void foo2(my_container<int const, char, float const> & my_cont)
{
// This should NOT be allowed: we do mind something being const to be treated by the
// called function as non-const.
//foo1(my_cont); /// BOOST_STATIC_ASSERT fails! Hurray!
}
void foo3(my_container<int, char, float> & my_cont)
{
// This should be allowed: we don't mind something being non-const to be treated by the
// called function as const.
foo2(my_cont); /// No complaints! Hurray!
}
int main(int argc, char* argv[])
{
my_container<int,char,float> foobar;
foo3(foobar);
return 0;
}
Upvotes: 0
Reputation: 41331
Without bordering on undefined behavior, this can be achieved with another level of indirection. Add a view class which references the original members. Constness can be added implicitly, but cannot be removed.
template<class A, class B, class C>
struct my_container
{
A a;
B b;
C c;
};
template <class A, class B, class C>
class my_container_view
{
A* a_;
B* b_;
C* c_;
public:
template <class A_, class B_, class C_>
my_container_view(my_container<A_, B_, C_>& source):
a_(&source.a), b_(&source.b), c_(&source.c)
{}
template <class A_, class B_, class C_>
my_container_view(my_container_view<A_, B_, C_>& source):
a_(&source.a()), b_(&source.b()), c_(&source.c())
{}
A& a() const { return *a_; }
B& b() const { return *b_; }
C& c() const { return *c_; }
};
void foo1(my_container_view<int, char, float const> my_cont)
{
my_cont.a() = 10;
my_cont.b() = 'a';
my_cont.c() /*= 3.14*/;
}
void foo2(my_container_view<int const, char, float const> my_cont)
{
my_cont.a() /*= 10*/;
my_cont.b() = 'a';
my_cont.c() /*= 3.14*/;
//foo1(my_cont); //not allowed
}
void foo3(my_container_view<int, char, float> my_cont)
{
my_cont.a() = 10;
my_cont.b() = 'a';
my_cont.c() = 3.14;
t
foo2(my_cont);
}
int main()
{
my_container<int, char, float> mc;
foo1(mc);
foo2(mc);
foo3(mc);
}
(I have my doubts, though, how much this is worth. Normally with classes, either you can modify all its members - and you just don't modify the ones you don't want to -, or you can't modify any. If you want this level of control, you'd rather pass each argument separately - the opposite of what you are doing.)
Upvotes: 1
Reputation: 153909
Neither should work. Different instantiations of a template are
unreleated types, with no implicit conversions between them. So
my_container<int, char, float const>
, my_container<int const, char,
float const>
and my_container<int, char, float>
are all unrelated
types, with no implicit conversions between them.
It should be possible to work out something using inheritance, using metaprogramming tricks to determine what you inherit from, but I'm not sure how, and I suspect that it would be more effort than it's worth.
Upvotes: 1