I'm working on some kind of simple reflection for c++ structs where i want to recursivly iterate over all member variables.
The code below almost does what i want but my compiler complians: "recursive type or function dependency context too complex" coming form aggregate_arity<MemberType>::size() which is based on Orients aggregate_arity implementation.
Example usage case:
struct B
{
SPVStruct;
var_t<float2_t, true> f4;
};
struct A
{
SPVStruct;
var_t<float2_t, true> f2;
var_t<float3_t, true> f3;
float d;
B b;
};
A a{};
InitializeStruct<A, true>(a);
Implementation:
struct TSPVStructTag {};
#ifndef SPVStruct
#define SPVStruct typedef TSPVStructTag SPVStructTag;
#endif
template< class, class = std::void_t<> >
struct has_spv_tag : std::false_type { };
template< class T >
struct has_spv_tag<T, std::void_t<typename T::SPVStructTag>> : std::true_type { };
template <class T>
void InitVar(T& _Member) {}
template <class T, bool Assemble>
void InitVar(var_t<T, Assemble>& _Member)
{
// actual stuff happening here
}
template <size_t N, class T, bool Assemble>
void InitStruct(T& _Struct)
{
if constexpr(N > 0u)
{
auto& member = get<N-1>(_Struct);
using MemberType = typename std::decay_t<decltype(member)>;
if constexpr(has_spv_tag<MemberType>::value)
{
constexpr size_t n = aggregate_arity<MemberType>::size(); // this is the complex recursion that blows up
InitStruct<n, MemberType, Assemble>(member);
}
else
{
InitVar(member);
InitStruct<N - 1, T, Assemble>(_Struct);
}
}
}
template <class T, bool Assemble>
void InitializeStruct(T& _Struct)
{
constexpr size_t N = aggregate_arity<T>::size();
InitStruct<N, T, Assemble>(_Struct);
}
Example
I use the has_spv_tag to mark structs that should be reflected. I can't wait for c++20 with actual reflection support :(
Thanks for your help!
Edit:
I got it to compile and changed the iteration order. Now a different problem comes up: constexpr size_t M = aggregate_arity::size() returns 0 even for the same type it returned the correct value earlier. i verified that the type is infact the same (first struct type B) by comparing the hash from typeid. How is it possible to that aggregate returns two different values for the exact same type?
template <class T, bool Assemble>
constexpr bool is_var_t(var_t<T, Assemble>& _Member) { return true; }
template <class T>
constexpr bool is_var_t(T& _Member) { return false; }
template <class T>
void InitVar(T& _Member) { std::cout << typeid(T).name() << std::endl; }
template <class T, bool Assemble>
void InitVar(var_t<T, Assemble>& _Member)
{
// actual stuff happening here
std::cout << typeid(T).name() << std::endl;
}
template <size_t n, size_t N, class T>
void InitStruct(T& _Struct)
{
std::cout << "n " << n << " N " << N << std::endl;
if constexpr(n < N)
{
decltype(auto) member = get<n>(_Struct);
using MemberType = std::remove_cv_t<decltype(member)>;
std::cout << typeid(MemberType).hash_code() << std::endl;
if (is_var_t(member))
{
InitVar(member);
InitStruct<n + 1, N, T>(_Struct);
}
else
{
constexpr size_t M = aggregate_arity<MemberType>::size();
InitStruct<0, M, MemberType>(member);
}
}
}
Edit 2: example for the new version: http://coliru.stacked-crooked.com/a/b25a84454d53d8de
Antony Polukhin pointed out the problem: MemberType still had the reference from get(_Struct). The code works with
MemberType = std::remove_reference_t<std::remove_cv_t<decltype(member)>>;
template <size_t n, size_t N, class T>
void InitStruct(T& _Struct)
{
if constexpr(n < N)
{
decltype(auto) member = get<n>(_Struct);
using MemberType = std::remove_reference_t<std::remove_cv_t<decltype(member)>>;
if constexpr(has_spv_tag<MemberType>::value)
{
InitStruct<0, aggregate_arity<MemberType>::size(), MemberType>(member);
}
else
{
InitVar(member);
}
InitStruct<n + 1, N, T>(_Struct);
}
}
I now use has_spv_tag<MemberType>::value to identify which member is a struct that i want to enumerate. There was also a bug with the order of InitStruct<n + 1, N, T>(_Struct);
Related
I need to provide format-magic to a class hierarchy and usually those things are passed around via pointer. This example here works currently on master:
#include <type_traits>
#include <sstream>
#include <fmt/format.h>
#include <fmt/ostream.h>
struct A {
virtual ~A() {}
virtual std::string name() const { return "A"; }
};
struct B : A {
virtual std::string name() const { return "B"; }
};
template <typename T>
struct fmt::formatter<T, std::enable_if_t<std::is_base_of<A, std::remove_const_t<std::remove_pointer_t<T>>>::value, char>> : fmt::formatter<std::string> {
template <typename FormatCtx>
auto format(const A* a, FormatCtx& ctx) {
return fmt::formatter<std::string>::format(a->name(), ctx);
}
};
template<class... T>
[[noreturn]] void err(fmt::format_string<T...> s, T&&... args) {
throw std::logic_error(fmt::format(s, std::forward<T>(args)...));
}
int main() {
A* a = new A();
B* b = new B();
const A* x = new A();
const B* y = new B();
fmt::print("{}, {}, {}, {}\n", a, b, x, y);
std::ostringstream oss;
fmt::print(oss, "{}\n", a);
err("{}, {}, {}, {}\n", a, b, x, y);
}
However, when I'm going to the latest release 8.1.1, I'm getting:
error: static assertion failed: Formatting of non-void pointers is disallowed.
Now my question is: What's the plan here? Can I do this in future versions of fmt or is this more of an accident?
This is definitely a regression. Supporting a hierarchy of classes can be done as documented in https://fmt.dev/latest/api.html#formatting-user-defined-types using references instead of pointers:
#include <type_traits>
#include <fmt/format.h>
struct A {
virtual ~A() {}
virtual std::string name() const { return "A"; }
};
struct B : A {
virtual std::string name() const { return "B"; }
};
template <typename T>
struct fmt::formatter<T, std::enable_if_t<std::is_base_of<A, T>::value, char>> :
fmt::formatter<std::string> {
template <typename FormatCtx>
auto format(const A& a, FormatCtx& ctx) {
return fmt::formatter<std::string>::format(a.name(), ctx);
}
};
int main() {
B b;
A& a = b;
fmt::print("{}", a); // prints "B"
}
I just want to print the two values of my structure, but can't compile my code - I get: no operator “<<” matches these operands.
#include <iostream>
using namespace std;
struct SCoor
{
int i, j;
bool operator == (const SCoor & tmp) const
{
return (i == tmp.i && j == tmp.j);
}
bool operator < (const SCoor & tmp) const
{
return (i < tmp.i || (i == tmp.i && j < tmp.j));
}
ostream& operator << (ostream &o) {
return o << i << " " << j;
}
};
int main()
{
SCoor tmp = { 3, 3 };
cout << tmp;
return 0;
}
How do I have to overload the operator "<<"?
You overload the << operator as a member function if you want your structure to be on the left hand side of the expression. So:
struct SCoor
{
// ...
SCoor& operator << (Stuff const& s) {
// ...
return *this;
}
};
// ...
Stuff stuff;
SCoor scoor;
scoor << s; // insert s into my SCoor
If you want to make your struct the right hand side of the << expression you have to define a standalone function:
std::ostream& operator<<(std::ostream& os, SCoor const& scoor)
{
return os << scoor.i << " " << scoor.j;
}
However it is quite common to make the external function a friend function and to define it in the struct definition:
struct SCoor
{
// ...
friend std::ostream& operator<<(std::ostream& os, SCoor const& scoor)
{
return os << scoor.i << " " << scoor.j;
}
};
// ...
SCoor scoor;
std::cout << scoor << '\n';
But it is not a member of your struct, it is just defined inside its definition for convenience.
Since C++17, a template function can return one type or another in function of an expression evaluated at compile-time:
template <size_t N>
constexpr auto f()
{
if constexpr (N == 1)
return 1.0; // return an double
else if constexpr (N == 2)
return std::array<double,N>(); // return an array of doubles
else
return -1; // return an int
}
Is there any equivalent for switch?
I've unsuccessfully tried:
template <size_t N>
constexpr auto f()
{
switch (N) // also switch constexpr (N) -> syntax error
{
case 1: return 1.0;
case 2: return std::array<double,N>();
default: return -1;
}
}
You could specialize the function template:
template <size_t N>
constexpr auto f()
{
// default case
return -1;
}
template<>
constexpr auto f<1>()
{
return 1.2;
}
template<>
constexpr auto f<2>()
{
return std::array<double,25>();
}
https://godbolt.org/z/dF_BSW
I am writing templated classes, specifically a templated class that stores another templated class in an array. I am having trouble with getting the pair members thing1 and thing2 set. They are getting printed out as garbage variables.
#ifndef ARRAY_H
#define ARRAY_H
#include"Pair.h"
#include<iostream>
using namespace std;
template<typename T>
class Array
{
template<typename U>
friend ostream & operator<<(ostream & out, const Array<U> &a);
private:
int size;
T* tPtr;
public:
Array(int s);
Array();
~Array();
T& operator[](int sub);
T operator[](int sub)const;
const Array<T> &operator =(const Array<T> &);
int getLength(){return size;};
};
template<typename T>
Array<T>::Array():size(0),tPtr(NULL)
{
}
template<typename T>
Array<T>::Array(int s):size(s)
{
tPtr = new T [size];
}
template<typename T>
Array<T>::~Array()
{
delete [] tPtr;
}
template<typename T>
T& Array<T>::operator[](int sub)
{
if(sub<0 || sub>size)
throw out_of_range("subscript out of range");
return tPtr[sub];
}
template<typename T>
T Array<T>::operator[](int sub)const
{
if(sub<0 || sub>size)
throw out_of_range("subscript out of range");
return tPtr[sub];
}
template<typename T>
const Array<T> &Array<T>::operator =(const Array<T> &right)
{
if(&right !=this)
{
if(size!=right.size)
{
delete [] tPtr;
size=right.size;
tPtr= new T[size];
}
for(int i =0;i<size;i++)
tPtr[i] = right.tPtr[i];
}
return *this;
}
template<typename U>
ostream & operator<<(ostream &out, const Array<U> &a)
{
for(int i =0;i<a.size;i++)
{
out<<a.tPtr[i];
}
out<<endl;
return out;
}
#endif
#ifndef PAIR_H
#define PAIR_H
#include <iostream>
using namespace std;
template<typename T,typename U>
class Pair {
private:
T thing1;
U thing2;
public:
Pair(){};
Pair( T thing1, U thing2) : thing1(thing1), thing2(thing2) {}
ostream & display(ostream & out=cout) const;
};
template<typename T,typename U>
ostream & Pair<T, U>::display(ostream & out) const {
return (out << "(" << thing1 << ", " << thing2 << ")");
}
template<typename T,typename U>
ostream & operator<<(ostream & out, const Pair<T,U> & pair) {
return pair.display(out);
}
#endif
#include "Pair.h"
#include "Array.h"
#include<string>
#include<iostream>
using namespace std;
template<typename T, typename U>
Array< Pair<T, U> >zip(Array<T> & lhs,Array<U> & rhs)
{
int zipLen = (lhs.getLength() < rhs.getLength() ? lhs.getLength() : rhs.getLength());
Array< Pair<T, U> >zipped(zipLen);
for (int i=0; i<zipLen; i++)
zipped[i] = Pair<T, U>(lhs[i], rhs[i]);
return zipped;//return array object
}
int main()
{
Array<int> a1(5);
Array<char>a2(3);
Array<Pair<int,char>>a3;
for(int i =1;i<5;i++)
a1[i-1]=i;
for(char ch='a';ch<='c';ch++)
a2[ch-'a']=ch;
a3=zip(a1,a2);//this is the line where I seem to lose all my data
cout<<a3;
system("pause");
return 0;
}
zipped is a local object. When the scope is over, its destructor will be called. Also the destructor of the two Array class objects inside zipped object will also get called. In the Array class the destructor, you are deleting the dynamic allocation. That's why you are see the garbage values. :)
Here's a quick template Dequeue object I made and it worked fine on mac terminal but visual c++ won't work for me. Sorry ahead of time if this is a dumb question, I'm a bit new to C++ and templates. Here's the code:
#include <iostream>
using namespace std;
template <typename T>
class Dequeue;
template <typename T>
class node;
template <typename T>
ostream& operator<<(ostream&, const Dequeue<T>&);
template <typename T>
class node{
public:
node<T>* next;
node<T>* prev;
T data;
node(T data=T(), node<T>* next=NULL, node<T>* prev=NULL);
};
template <typename T>
node<T>::node(T _data, node<T>* _next, node<T>* _prev){
data = _data;
next = _next;
prev = _prev;
}
template <typename T>
class Dequeue{
private:
node<T>* head;
node<T>* tail;
int _size;
void clear();
public:
Dequeue();
~Dequeue();
Dequeue(const Dequeue<T>&);
const Dequeue<T>& operator=(const Dequeue<T>&);
friend ostream& operator<< <>(ostream&, Dequeue<T>&);
void push_front(T&);
void push_back(T&);
void pop_front();
void pop_back();
T front();
T back();
int size();
bool isEmpty();
};
template <typename T>
Dequeue<T>::Dequeue(){
_size = 0;
head = NULL;
tail = NULL;
}
template <typename T>
Dequeue<T>::~Dequeue(){
clear();
}
template <typename T>
void Dequeue<T>::clear(){
while(_size > 0)
pop_front();
}
template <typename T>
Dequeue<T>::Dequeue(const Dequeue<T>& other){
head = NULL;
tail = NULL;
_size = 0;
*this = other;
}
template <typename T>
const Dequeue<T>& Dequeue<T>::operator=(const Dequeue<T>& other){
if(this = &other)
return *this;
clear();
node<T>* np = other.head;
while(np!=NULL){
node<T>* p = new node<T>(np->data);
if(head == tail == NULL)
head = tail = p;
else{
tail->next = p;
p->prev = tail;
}
tail = p;
np = np->next;
}
_size = other._size;
return *this;
}
template <typename T>
ostream& operator<<(ostream& out_str, Dequeue<T>& other){
node<T>* np = other.head;
while(np != NULL){
out_str << np->data << endl;
np = np->next;
}
return out_str;
}
template <typename T>
void Dequeue<T>::push_front(T& object){
node<T>* np = new node<T>(object);
np->next = head;
head->prev = np;
head = np;
_size++;
}
template <typename T>
void Dequeue<T>::push_back(T& object){
node<T>* np = new node<T>(object);
tail->next = np;
np->prev = tail;
tail = np;
_size++;
}
template <typename T>
void Dequeue<T>::pop_front(){
node<T>* np = head;
head = head->next;
delete np;
head->prev=NULL;
_size--;
}
template <typename T>
void Dequeue<T>::pop_back(){
node<T>* np = tail;
tail = np->prev;
delete np;
tail->next = NULL;
size--;
}
template <typename T>
T Dequeue<T>::front(){
return head->data;
}
template <typename T>
T Dequeue<T>::back(){
return tail->data;
}
template <typename T>
int Dequeue<T>::size(){
return _size;
}
template <typename T>
bool Dequeue<T>::isEmpty(){
if(size == 0)
return true;
else
return false;
}
int main(){
Dequeue<int> A;
A.push_back(5);
A.push_front(6);
A.push_back(7);
cout << A << endl;
cin.get();
return 0;
}
And here are the errors I'm getting:
1>------ Build started: Project: Kevin, Configuration: Debug Win32 ------
1> Dequeue.cpp
1>c:\users\kevin nguyen\documents\visual studio 2010\projects\kevin\kevin\dequeue.cpp(184): error C2664: 'Dequeue<T>::push_back' : cannot convert parameter 1 from 'int' to 'int &'
1> with
1> [
1> T=int
1> ]
1>c:\users\kevin nguyen\documents\visual studio 2010\projects\kevin\kevin\dequeue.cpp(185): error C2664: 'Dequeue<T>::push_front' : cannot convert parameter 1 from 'int' to 'int &'
1> with
1> [
1> T=int
1> ]
1>c:\users\kevin nguyen\documents\visual studio 2010\projects\kevin\kevin\dequeue.cpp(186): error C2664: 'Dequeue<T>::push_back' : cannot convert parameter 1 from 'int' to 'int &'
1> with
1> [
1> T=int
1> ]
========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========
Your issue is that in your declarations, you're trying to take a reference to T. In this case, the literals you are passing in to your function ( A.push_back(5), for example) is not necessarily something that can be taken a reference of. Your declaration should probably look more like this:
/* use `const` */
void Dequeue<T>::push_front( const T& object )
/* Same for push_back: use `const` */
void Dequeue<T>::push_back( const T& object )
Const will allow you to pass constant data as a reference. You make a copy of it anyways, so make sure all your declarations all the way down use const if they are taking a reference.
Also, it may be wise not to tell your T data parameter in your node to default to a freshly created T. That will force any data type you use with your Linked List to have a default constructor. Just take a const T& data instead, or no T at all.