// A.h
#pragma once
template <typename Derived, typename T>
struct IStuff;
template <typename Base>
struct StuffCommon;
struct StuffImpl;
struct A {
private:
int i = 12345;
public:
friend struct StuffCommon<IStuff<StuffImpl, A>>;
};```
```cpp
// Stuff.h
#pragma once
struct A;
template <typename Derived, typename T>
struct IStuff {
using value_type = T;
T data;
};
template <typename Base>
struct StuffCommon : Base {
void foo();
};
struct StuffImpl : StuffCommon<IStuff<StuffImpl, A>> {};```
```cpp
// Stuff.cpp
template <typename Base>
void StuffCommon<Base>::foo() {
std::cout << this->data.i << '\n'; // private member access
}
// explicit instantiation
template struct StuffCommon<IStuff<StuffImpl, A>>;```
#circular dependency issue with friend declarations
138 messages · Page 1 of 1 (latest)
When your question is answered use !solved to mark the question as resolved.
Remember to ask specific questions, provide necessary details, and reduce your question to its simplest form. For tips on how to ask a good question use !howto ask.
when I try to compile this code I get the following error
im not sure how to reformulate my code so that the incomplete type issue goes away
In your Stuff.h in the last line:
struct StuffImpl : StuffCommon<IStuff<StuffImpl, A>> {};
You never defined A, you just declared it.
At least that'd be my initial guess
the issue is that A's full definition is needed to make it a member of IStuff
Maybe you forgot to #include "A.h" in your Stuff.h?
A's full definition needs to be visible by the point StuffImpl inherits from a base that needs A to be a complete type
I guess that would be inconvenient because IStuff is not really supposed to know about A? but that would be the solution yes
if IStuff shouldn't know about A but StuffImpl needs to, then they need to reside in different files
in practice IStuff is its own header
I do think that's how it's supposed to be, at least in A.h there's all the declarations for all the definitions which are in Stuff.h, so if these two aren't connected I'm eating a broom
i should have separated it in my example ill edit it
what that says is that A needs to know about Stuff-related things, not the opposite
Oh yeah, you're right.
A.h has the declaration for all the stuffs in Stuff.h and Stuff.h has the declaration for the A defined in A.h.
Only realized the circular dependency now lmao
// A.h
#pragma once
template <typename Derived, typename T>
struct IStuff;
template <typename Base>
struct StuffCommon;
struct StuffImpl;
struct A {
private:
int i = 12345;
public:
friend struct StuffCommon<IStuff<StuffImpl, A>>;
};```
```cpp
// IStuff.h
#pragma once
template <typename Derived, typename T>
struct IStuff {
T data;
};```
```cpp
// Stuff.h
#pragma once
#include "IStuff.h"
struct A;
template <typename Base>
struct StuffCommon : Base {
void foo();
};
struct StuffImpl : StuffCommon<IStuff<StuffImpl, A>> {};```
```cpp
// Stuff.cpp
template <typename Base>
void StuffCommon<Base>::foo() {
std::cout << this->data.i << '\n'; // private member access
}
// explicit instantiation
template struct StuffCommon<IStuff<StuffImpl, A>>;```
this sounds like the correct solution, even with your edited setup
you went through the hassle of forward-declaring a bunch of things in A.h to avoid including Stuff.h there, if that's not to include A.h in Stuff.h then I don't know what it is for 😛
ok my example was not 1:1 with my actual code in practice because i just realized I had A included in StuffImpl.h in my actual codebase but the error persists
i need some time to figure dis out
i think its an issue with type constraints...'
// A.h
#pragma once
#include "Concept.h"
template <typename Derived, typename T>
struct IStuff;
template <IsStuff_A Base>
struct StuffCommon;
struct StuffImpl;
struct A {
private:
int i = 12345;
public:
friend struct StuffCommon<IStuff<StuffImpl, A>>;
};```
```cpp
// IStuff.h
#pragma once
template <typename Derived, typename T>
struct IStuff {
using value_type = T;
T data;
};```
```cpp
// StuffImpl.h
#pragma once
#include "IStuff.h"
#include "Concept.h"
#include "A.h" // StuffImpl can see full definition of A
template <IsStuff_A Base>
struct StuffCommon : Base {
void foo();
};
struct StuffImpl : StuffCommon<IStuff<StuffImpl, A>> {};```
```cpp
// StuffImpl.cpp
template <IsStuff_A Base>
void StuffCommon<Base>::foo() {
std::cout << this->data.i << '\n'; // private member access
}
// explicit instantiation
template struct StuffCommon<IStuff<StuffImpl, A>>;```
```cpp
// Concept.h
#pragma once
#include <concepts>
struct A;
template <typename T>
concept IsStuff_A = std::same_as<typename T::value_type, A>;```
introducing the IsStuff_A yields the error i was experiencing in my real codebase
if it was possible to define the friend classes out of line that would be nice...
bump :))
Jesus what a mess. Good luck!
can this be considered for Concept.h?
#pragma once
#include <concepts>
template <typename T, typename U>
concept IsStuff = std::same_as<typename T::value_type, U>;
usage:
template<IsStuff<A> Base>
struct StuffCommon : Base {
// ...
}
template<IsStuff<A> Base>
void StuffCommon<Base>::foo() {
// ...
}
the point in doing this is that hopefully by the time IsStuff is evaluated, along with instantiations that require A to be complete, A will be complete
this doesnt work unfortunately
and in fact, my actual codebase essentially was doing the same thing
where teh concept required you to input the "stuff" type
// A.h
#include "Concept.h"
struct A;
template <typename Derived, typename T>
struct IStuff;
template <IsStuff<A> Base>
struct StuffCommon;
struct StuffImpl;
struct A {
private:
int i = 12345;
public:
friend struct StuffCommon<IStuff<StuffImpl, A>>;
};```
```cpp
// IStuff.h
template <typename Derived, typename T>
struct IStuff {
using derived_type = Derived;
using value_type = T;
T data;
};```
```cpp
// Stuff.h
#include "IStuff.h"
#include "Concept.h"
#include "A.h" // StuffImpl can see full definition of A
template <IsStuff<A> Base>
struct StuffCommon : Base {
void foo();
};
struct StuffImpl : StuffCommon<IStuff<StuffImpl, A>> {};```
```cpp
// Stuff.cpp
template <IsStuff<A> Base>
void StuffCommon<Base>::foo() {
std::cout << this->data.i << '\n'; // private member access
}
// explicit instantiation
template struct StuffCommon<IStuff<StuffImpl, A>>;```
```cpp
// Concept.h
#include <concepts>
template <typename Derived, typename T>
struct IStuff;
template <typename T, typename U>
concept IsStuff = (std::derived_from<T, IStuff<typename T::derived_type, typename T::value_type>> && std::same_as<typename T::value_type, U>);```
here I applied the generalized concept (and an extra check to see if its actually "stuff" but thats just so the example looks a bit more practical)
What I did consider is removing the IsStuff concept as part of the StuffCommon type signature and making a static_assert inside the class body (something like static_assert(IsStuff<Base, A>). So in essence, its like a lazy type constraint and this would avoid me needing to have A fully defined to declare friend struct StuffCommon<IStuff<StuffImpl, A>>; within A. But im not sure I like this approach because its less readable and might have some unintended consequences but idk. Its also unintuitive why one would write it this way
It's kinda unintuitive and more importantly it would allow "invalid" specializations to be selected and instantiated, which will result in a compile error rather than a substitution/subsumption failure
the kind of problems that isn't a problem until it is one, and then it's unsolvable
can you post the error again with this setup?
so it seems the friend declaration in A causes StuffCommon<IStuff<StuffImpl, A>> to be instantiated and that's where problems are stemming from - I get why you titled this "circular dependency" now, I didn't know friend declarations caused instantiations
I don't know how you can easily solve this
does this make things better:
template<typename T>
friend struct StuffCommon;
(hoping you don't get something about declaration less constrained than previous declaration)
sorry for slow response had to take care of smth, this isnt allowed
assuming this is what u were suggesting
making it a template template parameter doesnt work either
remove <T> from it
simply this
well I'm out of ideas then, sorry 😦
thank u
ok last thing to try out:
template<typename T>
template<IsStuff<T> Base>
friend struct StuffCommon;
I'm guessing that counts as a partial specialization declaration though 🫠
but isnt this saying something different tho
I only want StuffCommon with A to be a friend
but any stuffcommon
having a regular friend declaration didn't work because you would run into the first errors that had in this post, so yeah we're trying out template friend declarations
which are broader sure but hey if they work... ¯_(ツ)_/¯
this is syntactically valid?!
templates of template declarations are thing yeah
well sorry for wasting your time so far anyway haha
nah i mean ive been stuck on it for 2 days its 0.01% of time spent, not wasted. lots of new ideas
I have the opposite viewpoint overall
what does having the constraint in the template achieve
gonna assume that the general structure makes sense
and that semantically you only want base types in stuff common that satisfy IsStuff<A>
why does that have to strictly be in the template definition
sure you advertise immediately and upfront that you want the constraint to be satisfied, but do you need this, or do you just want it
I want it. i feel like thats half the reason we use concepts if not for substitution or subsumption
because if the constraint is part of the template, the main thing this achieves is that every single specialization ever must satisfy the constraint, but at that point there cannot be concerns with picking the correct specialization
in your original design, there are no concerns about picking a wrong specialization
that is true yeah
so having the constraint in the template declaration directly achieves little from a practical point of view
why do you want it
there's a level of recursivity where things are just not possible
could you elaborate on that a little more
if you have
template <IsThing T>
struct foo;
then there are no concerns about picking the right specialization based on whether IsThing will be satisfied
because regardless of what specialization you try to introduce T always has to satisfy IsThing no matter what
because contextually the classes are for something like
template <typename Derived, typename T>
struct IObject {};
template <IsObject<int> Base>
struct IntObjectCommon : Base {};
struct IntObjectImpl : IntObjectCommon<IObject<IntObjectImpl, int>> {}```
would be weird to not constrain `IntObjectCommon` to take ints only
youre referring to substitution here if i understand correctly right?
but also this, i have multiple of the bottom two classes, FloatObjectCommon, FloatObjectImpl, StringObjectCommon, StringObjectImpl, etc...
its hard to explain all the background details without frontloading a bunch of paragraphs of context 🫠 (surely no one would want to attempt to answer that)
no I am not
this very much looks like an implementation internals
I get the general sentiment of wanting the constraint somewhere, but the way you're defining them and imposing them is too recursive, so I still raise the question of what it achieves to have the constraint then and there
is IntObjectCommon supposed to be exposed to the end user
and is he allowed to specialize it
only IntObjectImpl is front facing
if he isn't, and only the primary template is ever intended to be used, then to what extent does it hurt to put a static assert in the class body
rather than put an actual constraint directly on the template class definition
actually I guess I should check if the static assert is triggered in the relevant contexts
does not just putting it anywhere in the class body suffice
what other relevant contexts could there be
would there be a potential problem throwing the assert post-instantiation (as opposed to before with a concept)
thats what i was wondering
contexts that do not instantiate all the members and as such wouldn't trigger the static assert despite you probably preferring that the static assert be triggered and failing compilation
joyous things with what is/isn't immediate context
a static_assert in the class body probably will not trigger if you do something like using some_alias = StuffCommon<invalid_type>;
but for an internal implementation detail that isn't user facing, and specifically a type for which you want to have concrete instances of, that often isn't a true problem
it's just non-nice
yea i can see what you mean
how exactly do you define 'too recursive'
though it's often "nicer" than putting a static assert in every single member function
would there be a potential functional difference between putting a static assert in c lass body and one in every member function
if by that you mean having a static assert on the concept immediately after you instantiate a relevant specialization, it works, just that you have to remember to do that everytime
I don't have a formal definition for it, it's just based on requirements of what has to be defined before what point, and whether it's possible for these definitions to appear in the right order without breaking
crtp + concepts generally do not mesh together well, unless you break the recursion somewhere in some way
many concepts will typically require the type to be fully defined
which makes them not suitable for use in most crtp contexts
it changes how and when the static_assert is "instantiated"
if a member function is never used, then whatever is in its body doesn't matter
that's similar to how you can use a map with non-default-constructible mapped types, if you don't use []
i meant to say, if i already have the static assert at the start of the class in the body, would adding the same assert anywhere else in the class (i.e. a member function) have any functional difference
depending on what your real code and concepts look like, this is maybe doable, but tl;dr you'll need to break the recrusivity somewhere or write a concept that doesn't require the type to be complete
and often there's a point where you have to have some trust that someone didn't intentionally went out of there way to "lie"
probably not, I'd have to go over formal phrasing but within the body of a member function, if you have a static assert in the class body, then that static assert probably should be accounted for
but honestly this is overall too obscure a thing for me to assert anything
when you start going into that, there's too much I haven't actually read up on
@wind harness Has your question been resolved? If so, type !solved :)
solved kinda
!solved
Thank you and let us know if you have any more questions!
This thread is now set to auto-hide after an hour of inactivity
@wind harness an example of the iffy static assert in the class body https://godbolt.org/z/dhh8437M9
which is overall simpler to manage imo, for things where you don't critically need the constraint in the class template declaration
one possible way to break the recursion, though obviously only in the reduced example of the thread (so you'll be on your own for actual thing) https://godbolt.org/z/57eYzq6TW
to an extent it has "nicer" behaviour but I can't bring myself to recommend it
if the thing was user facing then actually maybe do it just to have an additional layer to prevent the user from messing up
but to an extent it feels like you add more complexity for something fairly brittle
(and if you were wondering, I was waiting for a compilation and got bored)
thats clever i didnt know this was a thing
i did end up going with static asserts in class body
type aliases dont necessarily skirt the assert, it just defers the problem till later
