I need to store a series of data-points in the form of (name, value), where the value could take different types.
I am trying to use a class template for each data-point. Then for each data-point I see, I want to create a new object and push it back into a vector. For each new type, I need to create a new class from the template first. But I can not store the objects created in any vector, since vectors expect the same type for all entries. The types I need to store can not be fitted in a inheritance hierarchy. They are unrelated. Also there can be more types created in future, and I do not want to change the storage service for each new type. Is there a way to create a heterogeneous container to store these entries?
Thank you!
C++17 and later.
std::any allows to hold any type, although it requires knowing the type that was stored to retrieve it.
If you have a set of known types, however, you may prefer std::variant:
using variant_type = std::variant<Foo, Bar, Joe>;
int func(variant_type const& v) // not template
{
auto const visitor = [](auto const& t)
{
if constexpr (std::is_same_v<Foo const&, decltype(t)>)
{
return t.fooish();
}
else
{
return t.barjoeish();
}
};
return std::visit(visitor, v);
}
A useful trick for quickly defining visitors:
template <typename... Ts> struct overload : Ts...
{
overload(Ts... aFns) : Ts(aFns)... {}
using Ts::operator()...;
};
template <typename... Ts> overload(Ts...) -> overload<Ts...>;
// Used as
auto const visitor = overload(
[](Foo const& foo) { return foo.fooish(); },
[](auto const& other) { return other.joebarish(); }
);
return std::visit(visitor, variant);
Pre-C++17.
boost::any has already been recommended, however it's for anything, so you can't expect much from it.
If you know the various types ahead of time, you're better using boost::variant.
typedef boost::variant<Foo, Bar, Joe> variant_type;
struct Print: boost::static_visitor<>
{
void operator()(Foo const& f) const { f.print(std::cout); }
template <class T>
void operator()(T const& t) const { std::cout << t << '\n'; }
};
void func(variant_type const& v) // not template
{
boost::apply_visitor(Print(), v); // compile-time checking
// that all types are handled
}
The boost library has probably what you're looking for (boost::any). You can roll your own using a wrapped pointer approach if you cannot use boost...
The problem with containers like this is that when you want to access something in the container, you have to determine its type and then cast it to the actual type somehow. This is ugly, inefficient and error-prone, which is why the #1 choice in C++ is to use inheritance, unless you have a very good reason not to - something I've never actually come across in my C++ career.
I was thinking that you could just have a Pair(type, void*) and write your own pop function that casts the void* depending upon the type describe in the pair and then shove these into whatever container catches your eye.
Related
note: this question was briefly marked as a duplicate of this, but it is not an exact duplicate since I am asking about std::optionals specifically. Still a good question to read if you care about general case.
Assume I have nested optionals, something like this(dumb toy example):
struct Person{
const std::string first_name;
const std::optional<std::string> middle_name;
const std::string last_name;
};
struct Form{
std::optional<Person> person;
};
and this spammy function:
void PrintMiddleName(const std::optional<Form> form){
if (form.has_value() && form->person.has_value() && form->person->middle_name.has_value()) {
std::cout << *(*(*form).person).middle_name << std::endl;
} else {
std::cout << "<none>" << std::endl;
}
}
What would be the best way to flatten this optional check?
I have made something like this, it is not variadic, but I do not care that much about that(I can add one more level(overload with membr3) if really necessary, and everything beyond that is terrible code anyway).
template<typename T, typename M>
auto flatten_opt(const std::optional<T> opt, M membr){
if (opt.has_value() && (opt.value().*membr).has_value()){
return std::optional{*((*opt).*membr)};
}
return decltype(std::optional{*((*opt).*membr)}){};
}
template<typename T, typename M1, typename M2>
auto ret_val_helper(){
// better code would use declval here since T might not be
// default constructible.
T t;
M1 m1;
M2 m2;
return ((t.*m1).value().*m2).value();
}
template<typename T, typename M1, typename M2>
std::optional<decltype(ret_val_helper<T, M1, M2>())> flatten_opt(const std::optional<T> opt, M1 membr1, M2 membr2){
if (opt.has_value() && (opt.value().*membr1).has_value()){
const auto& deref1 = *((*opt).*membr1);
if ((deref1.*membr2).has_value()) {
return std::optional{*(deref1.*membr2)};
}
}
return {};
}
void PrintMiddleName2(const std::optional<Form> form){
auto flat = flatten_opt(form, &Form::person, &Person::middle_name);
if (flat) {
std::cout << *flat;
}
else {
std::cout << "<none>" << std::endl;
}
}
godbolt
notes:
I do not want to switch away from std::optional to some better optional.
I do not care that much about perf, unless I return a pointer I must make copy(unless arg is temporary) since std::optional does not support references.
I do not care about flatten_has_value function(although it is useful), since if there is a way to nicely flatten the nested optionals there is also a way to write that function.
I know my code looks like it works, but it is quite ugly, so I am wondering if there is a nicer solution.
The operation you're looking for is called the monadic bind operation, and is sometimes spelled and_then (as it is in P0798 and Rust).
You're taking an optional<T> and a function T -> optional<U> and want to get back an optional<U>. In this case the function is a pointer to data member, but it really does behave as a function in this sense. &Form::person takes a Form and gives back an optional<Person>.
You should write this in a way that is agnostic to the kind of function. The fact that it's specifically a pointer to member data isn't really important here, and maybe tomorrow you'll want a pointer to member function or even a free function. So that's:
template <typename T,
typename F,
typename R = std::remove_cvref_t<std::invoke_result_t<F, T>>,
typename U = mp_first<R>>
requires SpecializationOf<R, std::optional>
constexpr auto and_then(optional<T> o, F f) -> optional<U>
{
if (o) {
return std::invoke(f, *o);
} else {
return std::nullopt;
}
}
This is one of the many kinds of function declarations that are just miserable to write in C++, even with concepts. I'll leave it as an exercise to properly add references into there. I choose to specifically write it as -> optional<U> rather than -> R because I think it's important for readability that you can see that it does, in fact, return some kind of optional.
Now, the question is how do we chain this to multiple functions. Haskell uses >>= for monadic bind, but in C++ that has the wrong association (o >>= f >>= g would evaluate f >>= g first and require parentheses). So the next closest chose of operator would be >> (which means something different in Haskell, but we're not Haskell, so it's okay). Or you could implement this borrowing the | model that Ranges does.
So we'd either end up syntactically with:
auto flat = form >> &Form::person >> &Person::middle_name;
or
auto flat = form | and_then(&Form::person)
| and_then(&Person::middle_name);
A different way to compose multiple monadic binds together is an operation that Haskell spells >=>, which is called Kleisli composition. In this case, it takes a function T -> optional<U> and a function U -> optional<V> and produces a function T -> optional<V>. This is something that is exceedingly annoying to write constraints for so I'm just going to skip it, and it would look something like this (using the Haskell operator spelling):
template <typename F, typename G>
constexpr auto operator>=>(F f, G g) {
return [=]<typename T>(T t){
using R1 = std::remove_cvref_t<std::invoke_result_t<F, T>>;
static_assert(SpecializationOf<R1, std::optional>);
using R2 = std:remove_cvref_t<std::invoke_result_t<G, mp_first<R1>>>;
static_assert(SpecializationOf<R2, std::optional>);
if (auto o = std::invoke(f, t)) {
return std::invoke(g, *o);
} else {
// can't return nullopt here, have to specify the type
return R2();
}
};
}
And then you could write (or at least you could if >=> were an operator you could use):
auto flat = form | and_then(&Form::person >=> &Person::middle_name);
Because the result of >=> is now a function that takes a Form and returns an optional<string>.
Let's look at what the optimal form of a flatten function would look like. By "optimal" in this case, I mean the smallest presentation.
Even in the optimal case, at the point of performing the flatten operation, you would need to provide:
The optional<T> object to flatten.
The flatten operation function name.
A list of names, in order, to be indirected from at each flattening step.
Your code is very close to optimal. The only issue is that each name in the "list of names" must contain the typename of the member you're accessing at that level, which is something that hypothetically could be computed using knowledge of T.
C++ has no mechanism to do any better than this. If you want to access a member of an object, you must provide the type of that object. If you want to indirectly do this, C++ allows member pointers, but getting such a pointer requires knowing the type of the object at the point when the member is extracted. Even offsetof gymnastics would require using the type name when you're getting the offset.
Reflection would allow for something better, as you could pass compile-time strings that static reflection could use to fetch member pointers from the type currently in use. But C++20 has no such feature.
You've got a lot of helper functions for something that is fundamentally a chainable operation. And C++ has things for chains: operators. So I'd probably (ab)use operator* for this.
For your specific case, all you need is
template<class class_t, class member_t>
std::optional<std::remove_cv_t<member_t>> operator*(
const std::optional<class_t>& opt,
const std::optional<member_t> class_t::*member)
{
if (opt.has_value()) return opt.value().*member;
else return {};
}
void PrintMiddleName2(const std::optional<Form> form){
auto middle = form * &Form::person * &Person::middle_name;
if (middle) {
std::cout << *middle;
}
else {
std::cout << "<none>" << std::endl;
}
}
But in reality you'd probably also want variants for non-optional members, getter methods, and arbitrary transforms, which I've listed here, though I'm not 100% certain they all compile properly.
//data member
template<class class_t, class member_t>
std::optional<std::remove_cv_t<member_t>> operator*(const std::optional<class_t>& opt, const std::optional<member_t> class_t::*member) {
if (opt.has_value()) return opt.value().*member;
else return {};
}
template<class class_t, class member_t>
std::optional<std::remove_cv_t<member_t>> operator*(const std::optional<class_t>& opt, const member_t class_t::*member) {
if (opt.has_value()) return {opt.value().*member};
else return {};
}
//member function
template<class class_t, class return_t>
std::optional<std::remove_cv_t<return_t>> operator*(const std::optional<class_t>& opt, std::optional<return_t>(class_t::*member)()) {
if (opt.has_value()) return opt.value().*member();
else return {};
}
template<class class_t, class return_t>
std::optional<std::remove_cv_t<return_t>> operator*(const std::optional<class_t>& opt, return_t(class_t::*member)()) {
if (opt.has_value()) return {opt.value().*member()};
else return {};
}
//arbitrary function
template<class class_t, class return_t, class arg_t>
std::optional<std::remove_cv_t<return_t>> operator*(const std::optional<class_t>& opt, std::optional<return_t>(*transform)(arg_t&&)) {
if (opt.has_value()) return transform(opt.value());
else return {};
}
template<class class_t, class return_t, class arg_t>
std::optional<std::remove_cv_t<return_t>> operator*(const std::optional<class_t>& opt, return_t(*transform)(arg_t&&)) {
if (opt.has_value()) return {transform(opt.value())};
else return {};
}
http://coliru.stacked-crooked.com/a/26aa7a62f38bbd89
As per the title, I'd like to understand how to make std::vector an Applicative but not a Monad (well, not yet). This is just for the sake of exploring and understanding Boost.Hana and functional programming.
Functor
In hana/ext/std/vector.hpp, the Functor instance of std::vector is commented out, however, it seems to work fine (compiler explorer) if I uncomment it or copy it in my code.
Applicative
However, there's no code in there to make it an Applicative (and, in turn, neither for making it a Monad), so I've looked into hana/concept/applicative.hpp to understand what must be implemented to do it myself. It needs an _implementation of ap and lift.
As regards lift, would the following be a good enough implementation?
template <>
struct lift_impl<ext::std::vector_tag> {
template <typename T>
static std::vector<std::decay_t<T>> apply(T&& value) {
return {std::forward<T>(value)};
}
};
On the other hand, since hana/lift.hpp contains a default implementation for Sequences,
template <typename S>
struct lift_impl<S, when<Sequence<S>::value>> {
template <typename X>
static constexpr decltype(auto) apply(X&& x)
{ return hana::make<S>(static_cast<X&&>(x)); }
};
I could alternatively just make std::vector a Sequence, which is easier:
template <>
struct Sequence<ext::std::vector_tag> {
static constexpr bool value = true;
};
Likewise, hana/ap.hpp also has an implementation for Sequences, but that uses hana::chain, as so it assumes that a Monad instance is defined in the first place.
But what if I want to make std::vector an Applicative but not a Monad? I should customize ap myself. Is the following correct?
template <>
struct ap_impl<ext::std::vector_tag> {
template<typename F,
typename X,
typename Y = std::decay_t<decltype((*std::declval<F>().begin())(*std::declval<X>().begin()))>>
static constexpr std::vector<Y> apply(F&& f, X&& x) {
std::vector<Y> result; result.reserve(f.size() * x.size());
for (auto const& f_ : f) {
for (auto const& x_ : x) {
result.emplace_back(f_(x_));
}
}
return result;
}
};
The result
At first sight I thought it works, because in this simple case (compiler explorer) it does give the correct result:
int main()
{
auto vplus = std::vector{std::plus<int>{}};
auto vec = hana::ap(
std::vector{std::plus<int>{}}, // wrapping 1 object in a vector
std::vector<int>{10,20},
std::vector<int>{1,2});
BOOST_HANA_RUNTIME_ASSERT(vec == std::vector<int>{11,12,21,22});
}
Disappointment
However I see some weakness in my solution:
I'm using std::vector instead of lift to wrap 1 function into a vector, which looks a bit non idiomatic, I believe;
As soon as I think about the case where using std::vector instead of lift would be required, i.e. when I want to apply more than 1 function, I hit against the fact that, for instance, I can't put std::plus and std::minus in the same vector because they are function objects of different types.
For hana::tuple (or std::tuple if I defined the required instances for it), it's a piece of cake, because it can hold heterogeneous types:
// TUPLE EXAMPLE
auto tuple = hana::ap(
hana::make_tuple(std::plus<int>{}, std::minus<>{}),
hana::make_tuple(10,20),
hana::make_tuple(1,2));
BOOST_HANA_RUNTIME_ASSERT(tuple == hana::make_tuple(11,12,21,22,9,8,19,18));
My question
Is there a way to make std::vector an Applicative in such a way that the // TUPLE EXAMPLE would work for std::vector?
Probably std::function and/or type erasure are necessary to accomplish the task?
I have some JSON files in which I define objects of various types. The types are given as a field within the objects. I want to load the file and for each JSON object, create a new class of that type and pass the rest of the JSON data to its constructor.
The issue is that I'd rather not have a huge case statement matching the type and creating an object of that type. Here are some of the possibilities I've considered:
Reflection. I don't know too much about it, but my understanding is that it might allow me to create a class in this manner. While I'm aware C++ doesn't provide this capability natively, I've seen a few libraries such as this one that might provide such functionality.
Create an enum of class types. Create a template function that takes a type parameter from this enum and creates an object of that type. Use something like smart_enum to convert the string field.
Option 2 seems like a good one but I haven't been able to get this working. I've done extensive googling, but no luck. Does anyone know how I might go about doing this, or if there is a better option which I have not considered? Apologies if this has been answered elsewhere, perhaps under a term which I do not know; I have spent quite a lot of time trying to solve this problem and had no luck.
Please let me know if I can provide any additional information, and thank you.
Edit: here's an example of what I've tried to get option 2 working.
#include <iostream>
#include <string>
enum class Animals {
Dog,
Cat
};
class Dog {
public:
std::string sound{"woof"};
};
class Cat {
public:
std::string sound{"meow"};
};
template<Animals animal> void make_sound() {
new animal();
cout << animal.sound << endl;
}
int main() {
make_sound<Animals::Dog>();
make_sound<Animals::Cat>();
std::exit(1);
}
There are a number of C++ JSON libraries that support mapping polymorphic or std::variant types based on some type selection strategy, which could rely on a type marker (e.g. "cat", "dog"), or alternatively the presence or absence of members. Lacking reflection, such libraries rely on traits. Typically the library provides built-in traits specializations for standard library types such as std::chrono::duration and std::vector, and supports custom specializations for user types. Some libraries offer convenience macros that can be used to generate the code for custom specializations.
The library ThorsSerializer has an example of encoding/decoding JSON for polymorphic types.
The library jsoncons has examples of encoding/decoding JSON for polymorphic types and the std::variant type
As noted in the comments, #1 is out, C++ lacks reflection (until P0194 gets adopted).
#2 still requires a big ol' switch block because you're still have to switch on a run-time type ID.
So, I'll propose #3: use a template to generate all those case statements you don't want to have to write (well, a map anyway).
This is the final code, which uses JSON for Modern C++ library for JSON parsing since that's the one that's available from godbolt :).
template <typename T, typename... Args>
struct option {
using type = T;
static_assert(std::is_constructible_v<T, Args...>, "constructor doesn't exist");
static T create(const nlohmann::json& json) {
return create_impl(json, std::index_sequence_for<Args...>{});
}
template <size_t... Is>
static T create_impl(const nlohmann::json& json, std::index_sequence<Is...>) {
return { json[Is].get<Args>()... };
}
};
template <typename...>
struct to_string {
using type = std::string_view;
};
template <typename... Options>
struct factory_builder {
using variant = std::variant<typename Options::type...>;
factory_builder(typename to_string<Options>::type... names)
: map { std::pair<std::string, std::function<variant(const nlohmann::json&)>> { names, [](const nlohmann::json& json) -> variant { return Options::create(json); } }... }
{ }
variant operator ()(const nlohmann::json& json) {
return map[json["type"].get<std::string>()](json["args"]);
}
std::map<std::string, std::function<variant(const nlohmann::json&)>> map;
};
Usage:
using factory_t = factory_builder<
option<Dog, double, std::string>, // Dog & its constructor argument types
option<Cat, int> // Cat & its constructor argument types
>;
factory_t factory("Dog", "Cat"); // String type identifiers for each option
auto my_object = factory( /* ... your JSON */ );
It assumes the JSON takes this form, where "type" is one of the string identifiers passed to the factory_builder constructor, and "args" is a list of arguments:
{
"type": "TheTypeIdentifier",
"args": [42, "whatever", false]
}
Demo: https://godbolt.org/z/3qfP9G
That's a lot of code, so let's break it down. First problem you need to solve is how to actually have a variable that can be more than one type, since C++ is strongly typed. C++17 provides std::variant for this, so we'll use that.
using result = std::variant<Dog, Cat>;
result example_result = Dog {};
example_result = Cat {};
Next, you need a way to generically describe, at compile time, how to construct an object: I used a simple struct with a template argument for the type, and a variable number of template arguments for the types going into that constructor:
template <typename T, typename... Args>
struct options;
Given a option<T, Args...>, how do you take a JSON array and pass those items to the constructor? With the nlohmann library, if you have a parsed JSON array called my_array and want to get index n and store it in an object of type T:
my_array[n].get<T>(); // accesses the array, converts, and returns a T&
To do that generically, I took the parameter pack of arguments and converted it into a parameter pack of increasing integers (0, 1, 2...) using std::index_sequence. Then I expanded the two parameter packs into T and n in the example above. It was convenient to put all this inside a static method of option<T, Args...>:
template <typename T, typename... Args>
struct option {
/* ... */
static T create(const nlohmann::json& json) {
return create_impl(json, std::index_sequence_for<Args...>{});
}
template <size_t... Is>
static T create_impl(const nlohmann::json& json, std::index_sequence<Is...>) {
return { json[Is].get<Args>()... };
}
};
That solves extracting arguments and calling a constructor for one type generically. Next problem is, how do you generate a function that switches on the type name and calls one of those Option<T, ...>::create functions?
For this solution, I used a map from strings to an std::function that takes JSON in and outputs our variant type:
template <typename... Options>
struct factory_builder {
// note: using a typedef "type" in options<T, Args...> that just points to T
using variant = std::variant<typename Options::type...>;
factory_builder(/* one string for each type */)
{
// TODO: populate map
}
variant operator ()(const nlohmann::json& json) {
return map[json["type"].get<std::string>()](json["args"]);
}
std::map<std::string, std::function<variant(const nlohmann::json&)>> map;
};
Now we just need to build that map. First, a detour: how do you ask for one string per type in a parameter pack? I used a helper type that takes a template argument and has a typedef that is always a string. Expand into that, and you get a parameter pack of string types:
template <typename...>
struct to_string {
using type = std::string_view;
};
Then, to populate the map, you can do that right from the initializer list:
using map_t = std::map<std::string, std::function<variant(const nlohmann::json&)>>;
factory_builder(...) : map {
typename map_t::value_type {
names,
[](const nlohmann::json& json) -> variant {
return Options::create(json);
}
}...
}
This is a little confusing, but it's expanding into something like this:
factory_builder(std::string dogName, std::string catName) : map {
std::pair<...> { dogName, [](auto& j) { return Dog(...); } },
std::pair<...> { catName, [](auto& j) { return Cat(...); } }
}
And that's it! Hope it helps.
How do I use different data types without the overhead of writing a line of code for each type?
Say if there's a template method that takes any data type.
And I want to pass in various data types (int, double, string, char, ...etc) without creating a line for each data type.
Is there an efficient way of looping through different data types and call the template method for each data type??
Sample Code:
template <typename T>
sorted_vector<T>::sorted_vector( sorted_vector<value_type> const& rhs )
: beg_( new value_type [rhs.size()] )
, end_( beg_ + rhs.size() )
, cap_( end_ )
{
std::copy( rhs.beg_, rhs.end_, beg_ );
}
So my task is to test the template with bugs but wanted to check all value_types.
And I wanted to test a vector, vector, vector, etc etc
You may loop using variadic template:
class Test
{
public:
template <typename T>
static void f()
{
// Your generic code to execute
std::cout << typeid(T).name() << std::endl;
}
};
template<typename F, typename ... Ts>
void Call()
{
std::initializer_list<int>({ (F::template f<Ts>(), 0)... });
}
And then call it that way:
Call<Test, int, char, char*>();
But I'm not sure it is more clear than
// Equivalent to
Test::f<int>();
Test::f<char>();
Test::f<char*>();
It is difficult to understand what you are really asking as your question is too broad. I would recommend to look into boost::variant and especially how pattern 'visitor' is implemented there. It does not mean you have to use exactly this library, but this could be a good start point how to implement such logic. Other candidates would be std::tuple and boost::any
Sound like a case for Template Metaprogramming.
Look at the foreach of Boost's MPL: foreach
Their example does something like you try to do: Calling a functor for different types and values of a list.
struct value_printer
{
template< typename U > void operator()(U x)
{
std::cout << x << 'n';
}
};
int main()
{
for_each< range_c<int,0,10> >( value_printer() );
}
I have a problem with duplication of identical code for const and non-const versions. I can illustrate the problem with some code. Here are two sample visitors, one which modifies the visited objects and one which does not.
struct VisitorRead
{
template <class T>
void operator()(T &t) { std::cin >> t; }
};
struct VisitorWrite
{
template <class T>
void operator()(const T &t) { std::cout << t << "\n"; }
};
Now here is an aggregate object - this has just two data members but my actual code is much more complex:
struct Aggregate
{
int i;
double d;
template <class Visitor>
void operator()(Visitor &v)
{
v(i);
v(d);
}
template <class Visitor>
void operator()(Visitor &v) const
{
v(i);
v(d);
}
};
And a function to demonstrate the above:
static void test()
{
Aggregate a;
a(VisitorRead());
const Aggregate b(a);
b(VisitorWrite());
}
Now, the problem here is the duplication of Aggregate::operator() for const and non-const versions.
Is it somehow possible to avoid duplication of this code?
I have one solution which is this:
template <class Visitor, class Struct>
void visit(Visitor &v, Struct &s)
{
v(s.i);
v(s.i);
}
static void test2()
{
Aggregate a;
visit(VisitorRead(), a);
const Aggregate b(a);
visit(VisitorWrite(), b);
}
This means neither Aggregate::operator() is needed and there is no duplication. But I am not comfortable with the fact that visit() is generic with no mention of type Aggregate.
Is there a better way?
I tend to like simple solutions, so I would go for the free-function approach, possibly adding SFINAE to disable the function for types other than Aggregate:
template <typename Visitor, typename T>
typename std::enable_if< std::is_same<Aggregate,
typename std::remove_const<T>::type
>::value
>::type
visit( Visitor & v, T & s ) { // T can only be Aggregate or Aggregate const
v(s.i);
v(s.d);
}
Where enable_if, is_same and remove_const are actually simple to implement if you don't have a C++0x enabled compiler (or you can borrow them from boost type_traits)
EDIT: While writing the SFINAE approach I realized that there are quite a few problems in providing the plain templated (no SFINAE) solution in the OP, which include the fact that if you need to provide more than one visitable types, the different templates would collide (i.e. they would be as good a match as the others). By providing SFINAE you are actually providing the visit function only for the types that fulfill the condition, transforming the weird SFINAE into an equivalent to:
// pseudocode, [] to mark *optional*
template <typename Visitor>
void visit( Visitor & v, Aggregate [const] & s ) {
v( s.i );
v( s.d );
}
struct Aggregate
{
int i;
double d;
template <class Visitor>
void operator()(Visitor &v)
{
visit(this, v);
}
template <class Visitor>
void operator()(Visitor &v) const
{
visit(this, v);
}
private:
template<typename ThisType, typename Visitor>
static void visit(ThisType *self, Visitor &v) {
v(self->i);
v(self->d);
}
};
OK, so there's still some boilerplate, but no duplication of the code that depends on the actual members of the Aggregate. And unlike the const_cast approach advocated by (e.g.) Scott Meyers to avoid duplication in getters, the compiler will ensure the const-correctness of both public functions.
Since your ultimate implementations are not always identical, I don't think there's a real solution for your perceived "problem".
Let's think about this. We have to cater for the situations where Aggregate is either const or non-const. Surely we should not relax that (e.g. by providing only a non-const version).
Now, the const-version of the operator can only call visitors which take their argument by const-ref (or by value), while the non-constant version can call any visitor.
You might think that you can replace one of the two implementations by the other. To do so, you would always implement the const version in terms of the non-const one, never the other way around. Hypothetically:
void operator()(Visitor & v) { /* #1, real work */ }
void operator()(Visitor & v) const
{
const_cast<Aggregate *>(this)->operator()(v); // #2, delegate
}
But for this to make sense, line #2 requires that the operation is logically non-mutating. This is possible for example in the typical member-access operator, where you provide either a constant or a non-constant reference to some element. But in your situation, you cannot guarantee that the operator()(v) call is non-mutating on *this!
Therefore, your two functions are really rather different, even though they look formally similar. You cannot express one in terms of the other.
Maybe you can see this another way: Your two functions aren't actually the same. In pseudo-code, they are:
void operator()(Visitor & v) {
v( (Aggregate *)->i );
v( (Aggregate *)->d );
}
void operator()(Visitor & v) const {
v( (const Aggregate *)->i );
v( (const Aggregate *)->d );
}
Actually, coming to think of it, perhaps if you're willing to modify the signature a bit, something can be done:
template <bool C = false>
void visit(Visitor & v)
{
typedef typename std::conditional<C, const Aggregate *, Aggregate *>::type this_p;
v(const_cast<this_p>(this)->i);
v(const_cast<this_p>(this)->d);
}
void operator()(Visitor & v) { visit<>(v); }
void operator()(Visitor & v) const { const_cast<Aggregate *>(this)->visit<true>()(v); }
Normally with this type of thing, it's possibly better to use methods that make sense. For example, load() and save(). They say something specific about the operation that is to be carried out via the visitor. Typically both a const and non-const version is provided (for things like accessors anyway), so it only appears to be duplication, but could save you some headache debugging later down the line. If you really wanted a workaround (which I wouldn't advice), is to declare the method const, and all the members mutable.
Add visitor trait to tell whether it's modifying or not (const or non-const use).
This is used by STL iterators.
You could use const_cast and change VisitorRead's method signature so it also take's const T& as a parameter, but I think that is an ugly solution.
Another solution - require the Visitor class to have a metafunction that adds const when it applies:
template <class Visitor>
static void visit(Visitor &v, typename Visitor::ApplyConst<Aggregate>::Type &a)
{
v(a.i);
v(a.d);
}