Boost bimap can't convert const CompatibleKey to Key& - c++

It is one of the first times I am using boost and I am getting an error saying
BaseKey boost::bimaps::container_adaptor::detail::key_to_base_identity<BaseKey,KeyType>::operator ()(Key &) const': cannot convert argument 1 from 'const CompatibleKey' to 'Key &
and
boost::multi_index::detail::ordered_index_impl<KeyFromValue,Compare,SuperMeta,TagList,Category, AugmentPolicy>::find': no matching overloaded function found
I know most of the STL errors or at least where could they come from, but I am not experienced enough with boost to know what could be going on here. The code I have is the following, it is used to convert the values from an enum to strings and vice versa.
file.h
namespace FOO_NS::BAR_NS
{
class FooClass
{
public:
enum class Enum
{
Enum1, Enum2, Enum3, Enum4
};
...
};
namespace
{
using results_bimap = boost::bimap<FooClass::Enum, std::string>;
using position = results_bimap::value_type;
const auto EnumsAsStrings = []() {
results_bimap result;
result.insert(position(FooClass::Enum::Enum1, "Enum1"));
result.insert(position(FooClass::Enum::Enum2, "Enum2"));
result.insert(position(FooClass::Enum::Enum3, "Enum3"));
result.insert(position(FooClass::Enum::Enum4, "Enum4"));
return result;
};
} // namespace
}//namespace FOO_NS::BAR_NS
file.cpp
using namespace FOO_NS::BAR_NS;
void doSmth()
{
...
std::string enumString = EnumsAsStrings().left.at(FooClass::Enum::Enum1); // Expected string "Enum1"
}
Do you see any misconception or misusage I have in this code so that this mentioned error happens?

You don't show enough code. Here's
assuming enum Enum{...}: http://coliru.stacked-crooked.com/a/20455e28883f93be
assuming enum class Enum{...}:
All I can think of is that you might have FooClass defined in an anonymous namespace as well, and you actually have disparate declarations of the enum which are not equivalent to the compiler.
Note that if this kind of setup would be the goal, you should be able to leverage the CompatibleKey overload by using a transparent comparator instead of the default (e.g. less<void> instead of less<FooClass::enum>).
Listing
Anti-bitrot:
#include <boost/bimap.hpp>
struct FooClass{
enum class Enum { Enum1, Enum2, Enum3, Enum4 };
};
namespace {
using results_bimap = boost::bimap<FooClass::Enum, std::string>;
using position = results_bimap::value_type;
auto const EnumsAsStrings = []() {
results_bimap result;
result.insert(position(FooClass::Enum::Enum1, "Enum1"));
result.insert(position(FooClass::Enum::Enum2, "Enum2"));
result.insert(position(FooClass::Enum::Enum3, "Enum3"));
result.insert(position(FooClass::Enum::Enum4, "Enum4"));
return result;
};
} // namespace
int main() {
std::string enumString =
EnumsAsStrings().left.at(FooClass::Enum::Enum1);
assert(enumString == "Enum1");
}

The following MCVE works, so it looks like you're not providing all the relevant information as to what your problem is:
Live Coliru Demo
#include <boost/bimap.hpp>
#include <string>
struct FooClass
{
enum Enum{Enum1,Enum2,Enum3,Enum4};
};
using results_bimap = boost::bimap<FooClass::Enum, std::string>;
using position = results_bimap::value_type;
const auto EnumsAsStrings = []() {
results_bimap result;
result.insert(position(FooClass::Enum1, "Enum1"));
result.insert(position(FooClass::Enum2, "Enum2"));
result.insert(position(FooClass::Enum3, "Enum3"));
result.insert(position(FooClass::Enum4, "Enum4"));
return result;
};
int main()
{
std::string enumString = EnumsAsStrings().left.at(FooClass::Enum1);
}

Okay, so at the end it wasn't anything related to this code (directly). I was calling the lambda like this EnumsAsStrings().left.at(FooClass::Enum1) and it couldn't implicitly convert from FooClass to FooClass::Enum and that was creating the errors. Thank you to everyone who tried to answer my question!

Related

Determine whether a type exists without feature-test macro

I'd like to determine whether a type exists without using a feature-test macro. Here's the idea using a macro:
namespace real
{
struct foo final { static constexpr const char* name = "real::foo"; };
}
#define real_foo_ 314
struct my_foo final { static constexpr const char* name = "my_foo"; };
namespace real
{
#if !real_foo_
using foo = my_foo;
#endif
}
That is, real::foo should to the "real" foo if it exists, otherwise my_foo will be used; subsequent code uses real::foo w/o knowing or caring whether it's the actual version or the replacement.
Achiving the same template meta-programming seems to be the right idea:
#include <type_traits>
namespace real
{
struct bar final { static constexpr const char* name = "real::bar"; };
}
struct my_bar final { static constexpr const char* name = "my_bar"; };
// https://devblogs.microsoft.com/oldnewthing/20190710-00/?p=102678
template<typename, typename = void>
constexpr bool is_type_complete_v = false;
template<typename T>
constexpr bool is_type_complete_v<T, std::void_t<decltype(sizeof(T))>> = true;
namespace real { struct bar; }
namespace real
{
using bar = std::conditional_t<is_type_complete_v<real::bar>, real::bar, my_bar>;
}
The above works as shown:
#include <iostream>
int main()
{
real::foo foo;
std::cout << foo.name << "\n";
real::bar bar;
std::cout << bar.name << "\n";
}
But removing the actual definition (not the forward) of real::bar causes compiler errors:
namespace real
{
//struct bar final { static constexpr const char* name = "real::bar"; };
}
error C2371: 'real::bar': redefinition; different basic types
message : see declaration of 'real::bar'
error C2079: 'bar' uses undefined struct 'real::bar'
Is there a way to make real::bar work like real::foo without relying on a feature-test macro? Note that as with real::foo, the name of real::bar can't be changed (e.g., to real::really_bar).
(Actual use case: C++14/C++17/C++20 library features implemented in C++11; once client code using std::filesystem::path has been written, it shouldn't have to change.)
Compiler complains on your code because you are trying to create two entities with same name. Consider changing
namespace real
{
struct bar final { static constexpr const char* name = "real::bar"; };
}
to something like
namespace real
{
struct absolutely_bar final { static constexpr const char* name = "real::bar"; };
}
...
namespace real { struct absolutely_bar; }
namespace real
{
using bar = std::conditional_t<is_type_complete_v<real::absolutely_bar>, real::absolutely_bar, my_bar>;
}
PS: creating such aliases is usually a bad pattern since it's not obvious.

How to access the type inside a std::variant?

I need to build a vector of a class that can have multiple type like this:
#include <variant>
#include <vector>
#include "Field.h"
using namespace std;
int main()
{
variant <int, float> v;
vector <variant<Field<int>, Field<string>, Field<float>>> fdList;
fdList[0].getName();
}
And this is header file Field.h:
#pragma once
#include <string>
#include <vector>
using namespace std;
template<class T>
class Field
{
public:
Field();
Field(string);
void setName(string);
string getName();
bool isPrime();
void toPrime();
void toForeign(Field);
~Field();
private:
string FD_Name;
vector <T> records;
bool isPrimeK = false;
string message;
};
template<class T>
string Field<T>::getName()
{
return FD_Name;
}
When I try to access getName() function, Visual Studio keeps giving me the following message error:
E0135 class "std::variant<Field, Fieldstd::string, Field>" has no member "getName"
C2039 'getName': is not a member of 'std::variant<Field,Fieldstd::string,Field>'
But it works just fine, if I define my vector like this:
vector <Field<int>> fdList;
fdList[0].getName();
How can I fix this?
For any issue about standard library, I recommend you to check document first.
You can see here about how to use std::variant.
In short, you cannot access the content in your std::variant like that because its type is std::variant but not the types you store in it. For your case, I think you may want to check what's inside first by calling std::variant::index() method, then get the value by calling std::get.
Calling a method on variant doesn't automatically call the method of the active alternative of variant. You'll have to visit the active alternative and invoke the corresponding handler. In your case, since you want to handle all of the potential active alternatives the same way, you can do:
std::visit([](const auto& field) {
field.getName();
// ...
}, fdList[0]);
Alternatively, you can wrap the variant in something like:
struct AnyField {
string getName() const {
return std::visit([](const auto& field) { return field.getName(); }, v);
}
std::variant<Field<int>, Field<string>, Field<float>> v;
};
then use them like you wanted to:
vector<AnyField> fdList;
fdList[0].getName();

C++ Mutually Recursive Variant Type (Again)

I have a problem similar to that described here: C++ Mutually Recursive Variant Type
I am trying to create a JSON representation in C++. Many libraries already offer excellent JSON representations and parsers that are very fast, but I am not reinventing this wheel. I need to create a C++ JSON representation that supports certain space optimizations under specific conditions. In short, if and only if a JSON array contains homogenous data, rather than storing every element as bloated variant types, I need compact storage of native types. I also need to support heterogeneous arrays and standard nested JSON objects.
The following is the "if wishes were horses, beggars would ride" version of the code, which is meant to clearly illustrate intent, but is obviously broken because types are used before any declaration exists. I want to avoid specifying the same information multiple times in types (i.e. Array, Object, and Value should not require duplicated type specifications). I also want to avoid any unnecessarily high run-time costs.
#include <string>
#include <unordered_map>
#include <vector>
#include <boost/variant.hpp>
#include <boost/variant/variant.hpp>
#include <boost/variant/recursive_wrapper.hpp>
class JSONDocument {
public:
using String = std::string;
using Integer = long;
using Float = double;
using Boolean = bool;
using Null = void *;
using Key = std::string;
using Path = std::string;
using Value = boost::variant<
Null,
String,
Integer,
Float,
Boolean,
Object,
Array
>;
using Object = std::unordered_map<Key,Value>;
using Array = boost::variant<
std::vector<Null>,
std::vector<String>,
std::vector<Integer>,
std::vector<Float>,
std::vector<Boolean>,
std::vector<Value> >;
private:
Value root;
class value_traversal_visitor : public boost::static_visitor<Value> {
public:
value_traversal_visitor( Path path ) : path(path) {}
Value operator()( Null x ) const {
if( path.empty() ) {
return x;
}
// otherwise throw ...
}
Value operator()( String x ) const {
if( path.empty() ) {
return x;
}
}
...
// special handling for Array and Object types
private:
Path path;
};
public:
Value get( Path path ) {
return boost::apply_visitor( value_traversal_visitor( path ), root );
}
...
};
As you can see, I am including the recursive_wrapper header. I have tried various invocations of boost::make_recursive_variant and boost::recursive_wrapper, but I always get compiler errors. I do not see how the answer from C++ Mutually Recursive Variant Type solves this, because in every attempt, I get compiler errors (from both gcc++ 5.3 and LLVM/clang++ 3.8) that almost exclusively reference Boost that essentially boil down to types not being convertible or declarations either conflicting or not existing. I would put one of my attempts along with specific compiler error messages here, but I wouldn't know which of the many attempts to use.
I'm hoping somebody can set me on the right path...
Thanks in advance!
Edit
Just to build on the accepted answer below, here is an example of a working skeleton for the types and their usages.
#include <string>
#include <unordered_map>
#include <vector>
#include <boost/variant.hpp>
#include <boost/variant/variant.hpp>
#include <boost/variant/recursive_wrapper.hpp>
using String = std::string;
using Integer = long;
using Float = double;
using Boolean = bool;
using Key = std::string;
using Value = boost::make_recursive_variant<
String,
Integer,
Float,
Boolean,
std::unordered_map<Key, boost::recursive_variant_>,
boost::variant<std::vector<String>,std::vector<Integer>,std::vector<Float>,std::vector<Boolean>,std::vector<boost::recursive_variant_> >
>::type;
using Object = std::unordered_map<Key, Value>;
using Array = boost::variant<std::vector<String>,std::vector<Integer>,std::vector<Float>,std::vector<Boolean>,std::vector<Value> >;
int main( int argc, char* argv[] ) {
Value v;
v = static_cast<Integer>( 7 );
Object o;
v = o;
Array a = std::vector<Integer>( 3 );
v = a;
return 0;
}
You could just use recursive_variant_ placeholder with make_recursive_variant.
Here's the gist:
using Value = boost::make_recursive_variant<
Null,
String,
Integer,
Float,
Boolean,
std::unordered_map<Key, boost::recursive_variant_>, // Object
std::vector<boost::recursive_variant_> // Array
>::type;
using Object = std::unordered_map<Key, Value>;
using Array = boost::variant<Value>;
Live Demo
Live On Coliru
As you can see there's unimplemented bits in the code (never write functions missing return statements!). Also note the simplifications in control flow for get and the private visitor implementation.
#include <boost/variant.hpp>
#include <boost/variant/recursive_wrapper.hpp>
#include <boost/variant/variant.hpp>
#include <string>
#include <unordered_map>
#include <vector>
class JSONDocument {
public:
struct Null { constexpr bool operator==(Null) const { return true; } };
using String = std::string;
using Integer = long;
using Float = double;
using Boolean = bool;
using Key = std::string;
using Path = std::string;
using Value = boost::make_recursive_variant<
Null,
String,
Integer,
Float,
Boolean,
std::unordered_map<Key, boost::recursive_variant_>, // Object
std::vector<boost::recursive_variant_> // Array
>::type;
using Object = std::unordered_map<Key, Value>;
using Array = boost::variant<Value>;
private:
Value root;
struct value_traversal_visitor {
Path path;
using result_type = Value;
result_type operator()(Value const &x) const {
if (path.empty()) {
return x;
}
return boost::apply_visitor(*this, x);
}
result_type operator()(Null) const { throw std::invalid_argument("null not addressable"); }
result_type operator()(String const &) const { throw std::invalid_argument("string not addressable"); }
// special handling for Array and Object types TODO
template <typename T> result_type operator()(T &&) const { return Null{}; }
};
public:
Value get(Path path) { return value_traversal_visitor{path}(root); }
};
int main() {}
CAVEATS
Note that you should NOT use void* for Null because all manner of unwanted implicit conversions
Note that you should probably not use unordered_map because
some JSON implementations allow duplicate property names
some JSON applications depend on the ordering of the properties
See also https://github.com/sehe/spirit-v2-json/blob/master/json.hpp#L37
Not a solution per se, but Here's a way to achieve variant recursivity using std::variant. I thought this might be of interest, since the stl doesn't provide any api for recursive, nor forward-declared types. Compiles using gcc 7.2 -std=c++17
#include <variant>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
struct Nil {};
struct vector1;
using var_t1 = variant<Nil, int, vector1>;
using var_t2 = variant<Nil, double, float, int, var_t1>;
struct vector1 {
vector<var_t2> v_;
};
struct print_var_t2;
struct print_var_t1 {
void operator()(const vector1& v);
void operator()(int) { cout << "int\n"; }
void operator()(const Nil&) { cout << "nil\n"; }
};
struct print_var_t2 {
void operator()(const Nil&) { cout << "Nil\n"; }
void operator()(int) { cout << "int\n"; }
void operator()(double) { cout << "double\n"; }
void operator()(float) { cout << "float\n"; }
void operator()(const var_t1& v);
};
void print_var_t1::operator()(const vector1& v) {
for_each(v.v_.begin(), v.v_.end(), [](const var_t2& x)
{
visit(print_var_t2{}, x);
});
}
void print_var_t2::operator()(const var_t1& v) {
visit(print_var_t1{}, v);
}
int main()
{
vector1 v1;
v1.v_.push_back(.1);
v1.v_.push_back(2.f);
v1.v_.push_back(3);
v1.v_.push_back(var_t2{3});
var_t1 var1 = v1;
std::visit(print_var_t1{}, var1);
return 0;
}

List WPA supplicant network properties using dbus-cpp

When trying to list network properties - https://w1.fi/wpa_supplicant/devel/dbus.html#dbus_network using dbus-cpp I get a number of errors about missing operator== for core::dbus::types::Variant
/usr/include/core/dbus/impl/object.h:185:17: required from ‘std::shared_ptr<core::dbus::Property<PropertyDescription> > core::dbus::Object::get_property() [with PropertyDescription = fi::w1::wpa_supplicant1::Network::Properties::Propertiez]’ /home/martin/ClionProjects/ang-wifi-controller/src/wpasupplicantclient.cpp:149:118: required from here /usr/include/c++/6/bits/stl_pair.h:364:51: error: no match for ‘operator==’ (operand types are ‘const core::dbus::types::Variant’ and ‘const core::dbus::types::Variant’)
{ return __x.first == __y.first && __x.second == __y.second; }
My code is based on dbus-cpp examples and http://quaintous.com/2015/08/30/cpp11-dbus/, but they only offer limited assistance.
The code representing Properties property is as follows:
namespace fi {
namespace w1 {
struct wpa_supplicant1 {
struct Network {
struct Properties {
struct Propertiez {
inline static std::string name() { return "Properties"; };
typedef Network Interface;
typedef std::map<std::string, core::dbus::types::Variant> ValueType;
static const bool readable = true;
static const bool writable = true;
};
};
};
And the offending line in .cpp is networkProxy->get_property<fi::w1::wpa_supplicant1::Network::Properties::Propertiez>();
I found that this question has already been asked at https://answers.launchpad.net/ubuntu/+source/dbus-cpp/+question/593271, but nobody offered any advice. Going through the code of packages listed by apt-cache rdepends libdbus-cpp5 also yielded no results.
I tried messing with the ValueType but it all resulted in runtime errors as the expected result probably truly is the map. It honestly seems like a bug in the library to me, but since this must be an obvious use case I am trying to find the mistake in my usage of the library. So what am I doing wrong?
Edit: Since I did not get any response, I am including minimal sample.
#include <core/dbus/bus.h>
#include <core/dbus/object.h>
#include <core/dbus/property.h>
#include <core/dbus/service.h>
#include <core/dbus/result.h>
#include <core/dbus/asio/executor.h>
#include <core/dbus/interfaces/properties.h>
#include <core/dbus/types/stl/string.h>
#include <core/dbus/types/stl/tuple.h>
#include <core/dbus/types/stl/vector.h>
#include <core/dbus/types/struct.h>
#include <core/dbus/types/variant.h>
using namespace std::chrono_literals;
using DBusDict = std::map<std::string, core::dbus::types::Variant>;
namespace fi {
namespace w1 {
struct wpa_supplicant1
{
struct GetInterface {
typedef wpa_supplicant1 Interface;
static const std::string &name()
{
static const std::string s("GetInterface");
return s;
}
inline static const std::chrono::milliseconds default_timeout() { return 1s; }
};
struct Iface
{
struct AddNetwork {
typedef Iface Interface;
static const std::string &name()
{
static const std::string s("AddNetwork");
return s;
}
inline static const std::chrono::milliseconds default_timeout() { return 1s; }
};
struct Properties
{
struct Networks
{
inline static std::string name()
{ return "Networks"; };
typedef Iface Interface;
typedef std::vector<core::dbus::types::ObjectPath> ValueType;
static const bool readable = true;
static const bool writable = false;
};
};
};
struct Network
{
struct Properties
{
struct Propertiez
{
inline static std::string name()
{ return "Properties"; };
typedef Network Interface;
typedef DBusDict ValueType;
static const bool readable = true;
static const bool writable = true;
};
};
};
};
};
};
namespace core {
namespace dbus {
namespace traits {
template <> struct Service<fi::w1::wpa_supplicant1> {
inline static const std::string &interface_name()
{
static const std::string s("fi.w1.wpa_supplicant1");
return s;
}
};
template <> struct Service<fi::w1::wpa_supplicant1::Iface> {
inline static const std::string &interface_name()
{
static const std::string s("fi.w1.wpa_supplicant1.Interface");
return s;
}
};
template <> struct Service<fi::w1::wpa_supplicant1::Network> {
inline static const std::string &interface_name()
{
static const std::string s("fi.w1.wpa_supplicant1.Network");
return s;
}
};
}
}
}
int main()
{
//bus
auto systemBus = std::make_shared<core::dbus::Bus>(core::dbus::WellKnownBus::system);
systemBus->install_executor(core::dbus::asio::make_executor(systemBus));
auto busThread = std::thread(std::bind(&core::dbus::Bus::run, systemBus));
//service
auto wpaService = core::dbus::Service::use_service<fi::w1::wpa_supplicant1>(systemBus);
auto wpaObjectPath = core::dbus::types::ObjectPath("/fi/w1/wpa_supplicant1");
auto wpaRootProxy = wpaService->object_for_path(wpaObjectPath);
//iface
auto ifacePath = wpaRootProxy->transact_method<fi::w1::wpa_supplicant1::GetInterface,
core::dbus::types::ObjectPath,
std::string>("wlan0"); //change this to your own wireless interface
auto wpaIfaceProxy = wpaService->object_for_path(ifacePath.value());
auto networkPaths = wpaIfaceProxy->get_property<fi::w1::wpa_supplicant1::Iface::Properties::Networks>();
//network
std::string ssid("network");
std::string password("password");
DBusDict args = {
{"ssid", core::dbus::types::Variant::encode(ssid)},
{"psk", core::dbus::types::Variant::encode(password)},
};
auto networkPath = wpaIfaceProxy->transact_method<fi::w1::wpa_supplicant1::Iface::AddNetwork,
core::dbus::types::ObjectPath,
DBusDict>(args);
auto networkProxy = wpaService->object_for_path(networkPath.value());
//get properties - uncomment line below to get compiler errors
//auto netProps = networkProxy->get_property<fi::w1::wpa_supplicant1::Network::Properties::Propertiez>();
while (true) {
continue;
}
}
Compile using: g++ $(pkg-config --cflags dbus-1 dbus-cpp) ./main.cpp $(pkg-config --libs dbus-1 dbus-cpp) -lpthread
Update:
dbus-cpp has an implementation for org.freedesktop.DBus.Properties.Get method.
Get:
auto resultVariant = dbus_object->invoke_method_synchronously
/*Method*/ <dbus::interfaces::Properties::Get,
/*Output Type*/ dbus::types::Variant,
/*Input Types*/ std::string, std::string>
("fi.w1.wpa_supplicant1.Network","Properties").value();
auto props = resultVariant.as<std::map<std::string, dbus::types::Variant>>();
Sadly, the Set method, while also implemented, does NOT seem to work with any ArgumentTypes with nested Variants within them. So:
a{sv} : std::map<std::string, core::dbus::types::Variant>
a{v} : std::vector<core::dbus::types::Variant>
in a Set method call will actually cause the program to crash. (Didn't test more)
Old post:
I ran into the same bug today and found a workaround for at least getting the properties value.
Instead of using
auto prop = dbus_object->get_property<fi::w1::wpa_supplicant1::Network::Properties::Propertiez>();
try
//returns std::map<std::string, core::dbus::types::Variant>>
auto props = dbus_object->get_all_properties<fi::w1::wpa_supplicant1::Network>();
auto prop = props["Properties"];
auto prop_value = prop.as<std::map<std::string, core::dbus::types::Variant>>();
As far as I understand the bug, dbus-cpp makes use of the
org.freedesktop.DBus.Properties interface to read out Properties.
So dbus_object->get_property() tries to invoke a org.freedesktop.DBus.Properties.Get and fails to compile because of the missing ==operator implementation. (Something it needs for casting the specific ValueType, I guess)
dbus_object->get_all_properties() invokes org.freedesktop.DBus.Properties.GetAll which does not need a specific ValueType, so it works.
Of course this is just a workaround to getting properties, since setting a property value is bound to the same shared_pointer as getting it.
As the documentation for fi.w1.wpa_supplicant1.Network.Properties.Properties says:
[...] All values are string type, e.g., frequency is "2437", not 2437.
So try to define DBusDict as follow:
using DBusDict = std::map<std::string, std::string>;

Enum in a class with strings

I'm trying to implement a class (C++) with an enum (with the permitted parameters). I got a working solution, but if I try to extend the functionality I get stuck.
Header data_location.hpp
class DataLocation
{
private:
public:
enum Params { model, period };
std::string getParamString(Params p);
};
Program data_location.cpp
string DataLocation::getParamString(Params p){
static const char * ParamsStrings[] = {"MODEL", "PERIOD"};
return ParamsStrings[p];
}
The array ParamsStrings should be generally available in the class, because I need a second method (with inverse function) returning the enum value given a string.
If I try to define the array in the header I get the error:
in-class initialization of static data member ‘const char* DataLocation::ParamsStrings []’ of incomplete type
Why is the type incomplete? The compiler is for sure able to counts the strings in the array, isn't it?
In case there is no way to get my code working, is there an other way? With 1) no XML; 2) no double definition of the strings; 3) not outside the class; 4) no in code programmed mapping.
In class (header) use keyword static and initialize it outside (.cpp) without the static keyword:
class DataLocation {
public:
enum Params { model, period };
string getParamString(Params p);
static const char* ParamsStrings[];
// ^^^^^^
};
const char* DataLocation::ParamsStrings[] = {"MODEL", "BLLBLA"};
//^^^^^^^^^^^^^^^^^^^^^^^^
The code you have posted is perfectly fine.
Here's the proof:
#include <iostream>
#include <string>
struct DataLocation
{
enum Params { model, period };
std::string getParamString(Params p){
static const char * ParamsStrings[] = {"MODEL", "PERIOD"};
return ParamsStrings[p];
}
};
int main()
{
auto a = DataLocation();
std::cout << a.getParamString(DataLocation::model) << std::endl;
return 0;
}
The error message you are getting is not to do with definition of a static data member in an inline function - that's allowed.
There's something else you're not showing us.
The main issue in my question (the second part) was that if I split the class in .hpp and .cpp the definition of the array (I mixed *char and string) has also to be split:
// data_location.hpp
class DataLocation {
static const char * ParamsStrings[];
}
// data_location.cpp
const char * ParamsStrings[] = {"MODEL", "PERIOD"};
At the end I introduced a consistency check to be sure that the number of values in enum growths as the number of strings. Because the array in C++ is somehow limited I had to go for a std::vector (to get the size).
Code for data_location.hpp
#ifndef DATA_LOCATION_HPP_
#define DATA_LOCATION_HPP_
#include <string>
#include "utils/dictionary.hpp"
extern const char* ENV_DATA_ROOT;
struct EDataLocationInconsistency : std::runtime_error
{
using std::runtime_error::runtime_error;
};
struct EDataLocationNotValidParam : std::runtime_error
{
using std::runtime_error::runtime_error;
};
class DataLocation
{
private:
std::string mRootLocation;
static const std::vector<std::string> msParamsStrings;
static bool msConsistenceCheckDone;
public:
DataLocation();
std::string getRootLocation();
std::string getLocation(Dictionary params);
enum Params { model, period, LAST_PARAM};
std::string Param2String(Params p);
Params String2Param(std::string p);
};
#endif
Code for data_location.cpp
#include "data_location.hpp"
#include <string>
#include <cstdlib>
using namespace std;
const char* ENV_DATA_ROOT = "DATA_ROOT";
bool DataLocation::msConsistenceCheckDone = false;
DataLocation::DataLocation() {
mRootLocation = std::getenv(ENV_DATA_ROOT);
if (not msConsistenceCheckDone) {
msConsistenceCheckDone = true;
if (LAST_PARAM+1 != msParamsStrings.size()) {
throw(EDataLocationInconsistency("DataLocation: Check Params and msParamsStrings"));
}
}
}
string DataLocation::getRootLocation() {
return mRootLocation;
}
string DataLocation::getLocation(Dictionary params) {
// to do
return "";
}
const vector<string> DataLocation::msParamsStrings = { "MODEL", "PERIOD", ""};
string DataLocation::Param2String(Params p) {
if (p>=msParamsStrings.size()) {
throw(EDataLocationNotValidParam("Parameter not found"));
}
return msParamsStrings[p];
}
DataLocation::Params DataLocation::String2Param(string p) {
for (int i = 0; i < msParamsStrings.size(); i++) {
if (p == msParamsStrings[i])
return (Params)i;
}
throw(EDataLocationNotValidParam("Parameter not found"));
}
And also a unit test:
#include <boost/test/unit_test.hpp>
#include "data_location.hpp"
#include <string>
using namespace std;
BOOST_AUTO_TEST_SUITE( data_location )
BOOST_AUTO_TEST_CASE(data_location_1) {
DataLocation dl;
auto s = dl.getRootLocation();
BOOST_CHECK_EQUAL(s, "/home/tc/data/forex" );
BOOST_CHECK_EQUAL(dl.Param2String(DataLocation::period),"PERIOD");
BOOST_CHECK_EQUAL(dl.String2Param("PERIOD"),DataLocation::period);
BOOST_CHECK_THROW(dl.String2Param("SOMETHING"), EDataLocationNotValidParam);
BOOST_CHECK_THROW(dl.Param2String((DataLocation::Params)100), EDataLocationNotValidParam);
}
BOOST_AUTO_TEST_SUITE_END()
C++ is very picky about what it will let you initialize inside of a class definition; there are some particularly non-intuitive rules surrounding static members. It all has to do with the ODR, and why all the rules are the way they are is not especially important.
To cut to the chase, making your array a static constexpr const member should shut the compiler up. With the C++11 standard, the restrictions were relaxed a bit, and one of the new stipulations was that static constexpr members can be initialized inline. This is perfect for your application, since the strings in your array are compile-time constants.
The recent g++ compiler which support C++0x or later compiles thus code. Pure C compile compiles, too. Because strings in initialization like {"MODEL", "PERIOD"}; implemented as const char * pointer to the char array.