new to this site and also C++ but hoping to see some guidance from everyone.
I had a pretty fun project idea to learn C++ digging deeper with APIs, classes, references, etc. and currently I have a working example of code where everything exist within the main.cpp file. The issue I am facing is that when i move the classes (inner and outer) to their own respective header files the code no longer compiles.
The reason for the nested classes is that the OuterAPI serves as the main entry point to the API and has many lower level APIs that can be then accessed beneath it (people, licenes, roles, etc). This way users of API would only have to create an object for the OuterAPI and then dot notation for underlying resource and method.
Here is the working example in the main.cpp
#include <iostream>
#include <nlohmann/json.hpp>
#include <cpr/cpr.h>
using json = nlohmann::json;
class OuterAPI {
private:
class InnerAPI {
private:
OuterAPI& api;
public:
InnerAPI(OuterAPI& a) :api(a) {}
json get() {
cpr::Response r = cpr::Get(
cpr::Url{ api.baseUrl + "resource" },
cpr::Bearer{ api.token }
);
return json::parse(r.text)
};
std::string token;
std::string baseUrl = "";
public:
InnerAPI people;
OuterAPI(std::string t) : token(t), people(*this) {}
};
int main(int argc, char** argv)
{
std::string token = "";
OuterAPI api(token);
json jsonData = api.people.get();
std::cout << jsonData.dump(4) << std::endl;
return 0;
}
Here is me moving everything to respective header/cpp files
OuterAPI.h
#pragma once
class OuterAPI {
private:
class InnerAPI;
std::string token;
std::string baseUrl = "";
public:
OuterAPI(std::string t);
~OuterAPI();
InnerAPI* people;
};
OuterAPI.cpp
#include "WebexAPI.h"
#include "PeopleAPI.h"
OuterAPI::OuterAPI(std::string t) : token(t) {
people = new InnerAPI(*this);
}
OuterAPI::~OuterAPI() { delete people; }
InnerAPI.h
#pragma once
#include <nlohmann/json.hpp>
#include <cpr/cpr.h>
#include "OuterAPI.h"
using json = nlohmann::json;
class OuterAPI::InnerAPI {
private:
OuterAPI& api;
public:
InnerAPI(OuterAPI& a);
json get();
};
InnerAPI.cpp
#include "InnerAPI.h"
OuterAPI::InnerAPI::InnerAPI(OuterAPI& a) : api(a) {}
json OuterAPI::InnerAPI::get() {
cpr::Response r = cpr::Get(
cpr::Url{ api.baseUrl + "resource" },
cpr::Bearer{ api.token }
);
return json::parse(r.text);
main.cpp (finally) - this is where the compiler error occurs at api.people.get() "expression must have class type but has type "OuterAPI::InnerAPI *"
int main(int argc, char** argv)
{
std::string token = "";
OuterAPI api(token);
json jsonData = api.people.get(); // COMPILER ERROR "expression must have class type but has type "OuterAPI::InnerAPI *"
std::cout << jsonData.dump(4) << std::endl;
return 0;
}
From this I believe the issue is associated with me having to define the InnerAPI object people as a pointer inside of OuterAPI but from here I cant seem to come to a resolution.
Also, feel free to critique my design as well, like I say I am new to C++ so want to make sure I can do a good job. Thanks.
In OuterAPI* you have declared people as a member of type InnerAPI*.
You can either call your API using api.people->get() or make the member a InnerAPI instead.
EDIT:
It seems the error, besides the pointer thing, comes from how you handle file includes. I managed to get a working version on REPL.it. I made slight adjustments so I wouldn't have to bring both libraries in so focus on the gist of it. Here it is:
OuterAPI.h
#pragma once
#include <string>
class OuterAPI {
private:
class InnerAPI;
std::string token;
std::string baseUrl = "";
public:
OuterAPI(std::string t);
~OuterAPI();
InnerAPI* people;
};
InnerAPI.j
#pragma once
#include "./OuterAPI.h"
class OuterAPI::InnerAPI {
private:
OuterAPI& api;
public:
InnerAPI(OuterAPI& a);
std::string get();
};
OuterAPI.cpp
#include "./OuterAPI.h"
#include "./InnerAPI.h"
OuterAPI::OuterAPI(std::string t) : token(t) {
people = new InnerAPI(*this);
}
OuterAPI::~OuterAPI() { delete people; }
InnerAPI.cpp
#include "./OuterAPI.h"
#include "./InnerAPI.h"
OuterAPI::InnerAPI::InnerAPI(OuterAPI& a) : api(a) {}
std::string OuterAPI::InnerAPI::get() {
return api.baseUrl + "resource";
}
Make sure you include everything you intend to use in every file where you intend to do so.
Separating declaration and definition is pretty common.
It's a way to reduce compile time on large projects.
Thankfully modules will soon™ make linking a thing of the past.
To address the error message: you declare people as a raw member pointer of the class OuterAPI… You cannot access a member through a pointer using operator ., you need to use operator ->.
Related
I am working through this problem I found on Git to brush up on some skills. Using friend is prohibited. C++ styling should be used compared to C.
Essentially, I cannot call the identify() function that belongs to the Brain member variable in my Human class. It just will not let me access it. If you can code this up, and explain where I am going wrong, that would be great.
Create a Brain class, with whatever you think befits a brain. It will have an Identify() function that returns a string containing the brain's address in memory, in hex format, prefixed by 0x.
Then, make a Human class, that has a constant Brain attribute with the same lifetime. It has an identify() function, that just calls the identity() function of its Brain and returns its result.
Now, make it so this code compiles and displays two identical addresses:
int main(){
Human bob;
std::cout << bob.identify() << "\n";
std::cout << bob.getBrain().identify() << "\n";
}
Here is what I have so far:
#pragma once
#include "Brain.h"
class Human
{
const Brain humanBrain;
public:
Human();
std::string identify();
};
#include "Human.h"
#include <iostream>
#include <string>
#include <sstream>
Human::Human()
{
this->humanBrain = new Brain;
}
std::string Human::identify()
{
Brain b = this->humanBrain.identify(); // This is essentially what I am trying to call--and I can't access it.
const Brain * ptr = humanBrain;
std::ostringstream test;
test << ptr;
return test.str();
}
#pragma once
#include <string>
#include <iostream>
class Brain
{
int age;
std::string gender;
void* ptr;
public:
Brain();
//std::string getBrain();
const std::string identify();
void setPtr(void* p);
};
#include "Brain.h"
#include <iostream>
#include <sstream>
Brain::Brain()
{
age = 10;
gender = "male";
}
const std::string Brain::identify()
{
//const Brain* bPtr = &this;
const Brain* bPtr = this;
ptr = this;
std::ostringstream test;
test << &bPtr;
std::string output = "Brain Identify: 0x" + test.str();
return output;
}
Your Human::humanBrain member is declared as type const Brain, which is correct per the instructions, however your Brain::identify() method is not qualified as const, so you can't call it on any const Brain object. This is the root of the problem that you are having trouble with.
In addition, there are many other problems with your code, as well:
Human::humanBrain is not a pointer, so using new to construct it is wrong. And, you don't need a pointer to get the address of a variable anyway. Nor do you actually need a pointer to the member at all in this project.
Human lacks a getBrain() method, so bob.getBrain() in main() will not compile, per the instructions.
Human::identify() is calling humanBrain.identify(), which returns a std::string as it should, but is then assigning that string to a local Brain variable, which is wrong (not to mention, you are not even using that variable for anything afterwards). The instructions clearly state that Human::identity() should simply call Brain::identify() and return its result, but you are not doing that.
Brain::identify() is printing the address of its local variable bPtr rather than printing the address of the Brain object that identify() is begin called on, per the instructions.
With all of that said, try something more like this instead:
Human.h
#pragma once
#include "Brain.h"
#include <string>
class Human
{
const Brain humanBrain;
public:
Human() = default;
std::string identify() const;
const Brain& getBrain() const;
};
Human.cpp
#include "Human.h"
std::string Human::identify() const
{
return humanBrain.identity();
}
const Brain& Human::getBrain() const
{
return humanBrain;
}
Brain.h
#pragma once
#include <string>
class Brain
{
int age;
std::string gender;
public:
Brain();
std::string identify() const;
};
Brain.cpp
#include "Brain.h"
#include <sstream>
Brain::Brain()
{
age = 10;
gender = "male";
}
std::string Brain::identify() const
{
std::ostringstream test;
test << "Brain Identify: 0x" << this;
return test.str();
}
I'm pretty new to coding, and I'm following this French tutorial that is basically making a RPG game used through console.
So I got a Character class in a .cpp file and .h, another .h .cpp couple of files for the weapons, and my main.
I got a function on my CPP file that's like this :
void Character::attack(Character &target)
{
target.takeDamage(m_weapon.getDamage());
}
So far, in understand I'll use it in this way : davids.attack(goliath) and I made an alias so I can easily use the takeDamage function of my target using its alias.
but I'd like to add a console line like David attacked Goliath and made X damages in that function.
And it looks like I can't call another function like
string Character::getname
{return name}
due to the fact I use 2 different characters.
Is there a way to get around that or should I get them both an alias?
Assuming the Character class has field like name you can access the field in your method by using this->name (it will acces name of the object which called the method, in this case it is David) and the target.name will be "Goliath".
If C++17 is available to you I recommend using CRTP.
#include <iostream>
#include <string>
#include <string_view>
template <typename Derived>
class Character{
static constexpr std::string_view CName = "Character";
public:
constexpr const std::string_view& getName() { return Derived::CName; }
template<typename K>
void Attack(Character<K>& Target) {
std::cout << getName() << " attacked " << Target.getName() << "\n";
}
};
class David : public Character<David>{
public:
static constexpr std::string_view CName = "David";
};
class Goliath : public Character<Goliath>{
public:
static constexpr std::string_view CName = "Goliath";
};
int main(int a, char** argv){
David d;
Goliath g;
d.Attack(g);
return 0;
}
Output:
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.
Let's say, I've got a following simple code:
Main.cpp
#include "A.h"
// For several reasons this must be a global variable in the project
A a1;
int _tmain(int argc, _TCHAR* argv[])
{
// Another stuff
return 0;
}
A.h
#pragma once
#include <string>
class A
{
private:
// The following works normal if we use simple types like int and etc.
static std::string myString;
public:
A();
};
A.cpp
#include "stdafx.h"
#include "A.h"
// This executes after A::A(), so we are losing all the modifyed content
// If we skip the ="test" part, the string is going to be empty
std::string A::myString = "test";
A::A()
{
// Here myString == ""
myString += "1";
}
The problem is obvious: I cannot use static variables in a constructor of class A in this case as they don't save the changes. Although I need them in order to process some data.
Please, suggest me a solution.
It sounds like you are trying to force the initialization of the static to happen before the constructor is called. The last time I encountered this problem, the only reliable fix was to wrap the static inside a function.
Change the declaration to a function returning reference to string.
static std::string& myString();
Change the definition to a function like this:
std::string& A::myString() {
static std::string dummy = "test";
return dummy;
}
Change your constructor to say:
myString() += "1";
I do not currently have an MSFT compiler handy, so you may have to tweak this a little bit, but this basically forces on-demand initialization of static.
Here is a very short test programming demonstrating how this works:
#include <string>
#include <stdio.h>
std::string& myString() {
static std::string dummy = "test";
return dummy;
}
int main(){
myString() += "1";
printf("%s\n", myString().c_str());
}
I have following code, which i taken from Boost and simplified for my project. Please accept my aplogies for pasting complete code, i done it so that it will be easy to answer my question. While compiling following code in VS 2008 i am getting followoing error.
error C2064: term does not evaluate to a function taking 3 arguments
I am expecting addOptions retruns OptionsInit object which call function operator with three arguments but that is not happening, can any one please find bug. Thanks in advance.
namespace MyInfrastructure
{
namespace Internal
{
class OptionDescrp;
class OptionsInit;
}
class OptionsCollection
{
public:
OptionsCollection(std::string optCollName);
Internal::OptionsInit addOptions();
private:
// avoid copying and assignment.
// Prohibit copy
OptionsCollection( const OptionsCollection& );
OptionsCollection& operator = (const OptionsCollection& );
void add(Internal::OptionDescrp* desc) {m_options.push_back(desc);}
std::vector<Internal::OptionDescrp* > m_options;
std::string m_optCollName;
friend class Internal::OptionsInit;
};
}
////////////
#include <string>
#include <vector>
#include <assert.h>
#include "PrgmOptions.h"
namespace MyInfrastructure
{
namespace Internal
{
class OptionDescrp
{
public:
OptionDescrp(std::string pcOptname, std::string description, bool isOptValueReq);
virtual ~OptionDescrp(){ };
private:
std::string m_shortName; // option short name.
std::string m_longName; // option long name.
std::string m_description;// option description.
};
class OptionsInit
{
public:
OptionsInit(OptionsCollection* coll){ owner = coll; }
OptionsInit& operator()(std::string name, std::string description, bool isOptValReq);
private:
OptionsCollection* owner;
};
}
/////
namespace MyInfrastructure
{
OptionsCollection::OptionsCollection(std::string optCollName) : m_optCollName(optCollName) {}
Internal::OptionsInit OptionsCollection::addOptions()
{
return Internal::OptionsInit(this);
}
}
namespace MyInfrastructure
{
namespace Internal
{
// Class Options description definitions.
OptionDescrp::OptionDescrp(std::string pcOptname, std::string description, bool isOptValueReq)
: m_description(description)
{
std::string name(pcOptname);
std::string::size_type n = name.find(',');
if (n != std::string::npos)
{
assert(n == name.size()-2);
m_longName = name.substr(0, n);
m_shortName = '-' + name.substr(n+1,1);
}
else
{
m_longName = name;
}
}
// Class Options Init definitions.
OptionsInit& OptionsInit::operator()(std::string name, std::string description, bool isOptValReq)
{
OptionDescrp* opt = new OptionDescrp(name, description, isOptValReq);
owner->add(opt);
return *this;
}
}
}
//////
int main(void)
{
MyInfrastructure::OptionsCollection desc("myoptions");
**desc.addOptions()("help", "produce help message", false); // error is thrown here**
return 0;
}
The example code in the question compiles without errors with Visual 2008, gcc, Visual 2003 when we copy all in a single file.
You have error C2064, it is probably because you either have a #define or another definition somewhere in other headers that you did not include in your sample, or that somehow you are not compiling exactly the sample code.
Try to copy all the sample code in a single file and compile that.
Interesting code: OptionsInit returned by addOptions() is a temporary. You are then calling a non-const method on it, which is allowed, but it returns a non-const reference to itself which is also allowed because it's a non-const method. But that means essentially you "backdoor" binding a non-const reference to a temporary...
I assume the two asterisks before desc.addOptions are not really in your code as there is no operator* overloaded here.
Perhaps if you make operator() const and return const-reference it will work.
problem is with VS2008. I compiled with VS2010, it compiled fine. Thanks all for the inputs.