I do not know how to do it and everything I try just doesn't work and I just can't know why. Here's what I am trying:
template <typename T, typename... Args >
concept IsSameType = std::conjunction_v<std::is_same<T, Args>...> || std::conjunction_v<std::is_same<T&, Args>...>;
template <typename T, typename... Args >
concept IsDifferentType = (!(std::conjunction_v<std::is_same<T, Args>...>)) && (!(std::conjunction_v<std::is_same<T&, Args>...>));
And then the constructors look like this:
template <IsDifferentType<T>... Args>
List(Args&&... args)
template <IsSameType<T> ... Args>
List(Args&&... args)
I don't think their bodies are relevant, where I get the error it means that the wrong constructor was called.
The error is: error C2440: 'initializing': cannot convert from 'initializer list' to 'List::Node' message : Invalid aggregate initialization
Here:
Node* createNode(Args&&... args) {
return new Node{ {std::forward<Args>(args)...}, next };
}
Node is this:
struct Node {
T value;
Node* next;
};
T is a Person class in this case and so it takes a few arguments and a person is created here: {std::forward(args)...} in createNode(Args&&... args)
But this should be called in the case that the arguments are not of the same type and right now I am calling the constructor with arguments of the same type. Like this:
Person chris{ "Name", "Surname", 10, 30 };
Person chris2{ chris };
Person chris3{ chris2 };
List<Person> list{ chris, (chris2), chris3};
and yet the wrong constructor is called because the constructor with all Arguments forced to be T is the only one that calls the createNode function, the other constructor calls another function. All of it is for trying to learn. I believe what I am trying to do would be confusing and a bad idea but still I want one constructor that will take the arguments and will create a Person internally and one that can take as many arguments(for example, Persons) and just push them in the list.
*I might actually want to allow for types to be different but relevanant, for example I don't want *the compiler to say T is Person and Args is Person& and so they are not the same because in that *case they may have to be considered the same for this to work.
I think I want this to work like
template <typename... Args>
List(Args&&... args)
would but of course I need one of them to work for List list{ chris, (chris2), chris3}; and also any combination using std::move() on the first, second... etc argument(or all of them, or the first 2 etc, which seems to work when I use just typename ... Args but of course this would cause problems if I want to have 2 different constructors that have the same arguments.
So I need one of them to work for List list{ chris, (chris2), chris3}; and another to work for different types of arguments that are not T so as to create an element of T based on the arguments inside createNode function...
I hope what I am asking is clear and please forgive me if my questions are not clear enough or seem low effort only asking, asking is always a last resort for me I just don't know how to figure it out myself. I also hope I am not asking for something that's a lot more complicated that I imagine, it's just that I feel like there is a simple solution that I just don't know about and for some reason this bothers me greatly. Somehow my mind is stuck at this and I don't want to do anything until I find some way to do that. How do I find some way to do that?
Anyway, I hope I have been clear enough on what I want to do I tried to explain as best as I could. I am also sorry if the answer already exists and I should have been able to find it. The problem is that I may see it and not understand how to use it.
I think I already explained what I have tried. I actually tried some other slightly different ways but I don't remember them. Right now I am trying to create 2 concepts to limit the arguments. I would also like the constructors to only be considered when the appropriate types are there as to avoid any collisions between them. I have just remembered, I also tried std::enable_if_t but I can't remember exactly how, I have these 2 in my comments: std::enable_if_t<!std::conjunction_v<std::is_same<T, Ts>...>> typename std::enable_if_t<std::conjunction_v<std::is_same<T, Args>...>>
So something like that... I was expecting that the 2 constructors would be called correctly according to what types where passed such that if T=Person and the arguments are of type Person or relevant then the constructor that deals with that would be called but one I do it, the parameter pack no longer seems to work the same or something...
I just tried this and it seems to be working as intended:
template <typename T, typename Type>
concept IsSameType = std::is_same_v<std::remove_cvref_t<T>, Type>;
template <typename T, typename Type>
concept IsDifferentType = !std::is_same_v<std::remove_cvref_t<T>, Type>;
template < IsDifferentType<T>... Args >
List(Args&&... args)
To the person who took the time to right the first(and only thus far) response, thank you very much! I also thank the community overall because there's often an answer to what I want to do here. The more I understand, I think the easier it will become for me to find an answer here :)
In
template <IsDifferentType<T>... Args>
List(Args&&... args)
the concept is applied to each parameter individually. This can work for the case where you test for same elements, but for the negation you need to take every single template argument into consideration at once and therefore no conjunction can be used.
The constraint is basically a conjunction, since all the individual constraints for the template arguments need to be met for the function to be applicable. The constraint in the constructor signature above is basically
template<class...Args>
requires (IsDifferentType<Args, T> &&...)
List(Args&&...args)
So the following code should work and also simplify the concept a bit:
template<class T, class Type>
concept IsSameType = std::is_same_v<std::remove_cvref_t<T>, Type>;
template<class T>
class List
{
public:
template<IsSameType<T> ... Args> // case: Every single template parameter is a T
List(Args&&...args)
{
std::cout << "multiple element types\n";
}
template<class ...Args>
requires !(IsSameType<Args, T> && ...)
List(Args&&...args)
{
std::cout << "constructor for single element\n";
}
List() // requires special treatment since otherwise the first constructor would be used, if no parameter are passed
{
std::cout << "default constructor\n";
}
};
struct Person
{
char const* m_name;
};
int main() {
Person chris{ "Name" };
Person chris2{ chris };
Person chris3{ chris2 };
std::cout << "list from chrises\n";
List<Person> list{ chris, (chris2), chris3 };
std::cout << "list from chrises & something else\n";
List<Person> list2{ chris, (chris2), chris3, nullptr };
std::cout << "default constructor\n";
List<Person> list3;
}