Why Not Infer Template Parameter from Constructor

Why not infer template parameter from constructor?

I think it is not valid because the constructor isn't always the only point of entry of the class (I am talking about copy constructor and operator=). So suppose you are using your class like this :

MyClass m(string s);
MyClass *pm;
*pm = m;

I am not sure if it would be so obvious for the parser to know what template type is the MyClass pm;

Not sure if what I said make sense but feel free to add some comment, that's an interesting question.

C++ 17

It is accepted that C++17 will have type deduction from constructor arguments.

Examples:

std::pair p(2, 4.5);
std::tuple t(4, 3, 2.5);

Accepted paper.

How to infer template parameter Func from argument of constructor?

The compiler is not able to deduce Func from your second constructor, so you need to tell it how to determine the Func class parameter when the first constructor is not viable. You can do that by adding a deduction guide:

template<typename T, typename Step, std::enable_if_t<!std::is_invocable_r_v<T, Step, const T &>, int> = 0>
StepRange(T, T, Step) -> StepRange<T, std::function<T(const T&)>>;

This will deduce Func to std::function<T(const T&)> whenever the first constructor is disabled.

Or, if you actually only want the second constructor to be used if the types of all three arguments match, then you can write instead

template<typename T>
StepRange(T, T, T) -> StepRange<T, std::function<T(const T&)>>;

Of course there are some edge cases to be considered in either case.

template argument deduction for constructors

Template argument deduction works for any function, including the constructor. But you can't deduce the class template parameters from arguments passed to the constructor. And no, you can't do it in
C++0x either.

struct X
{
template <class T> X(T x) {}
};

template <class T>
struct Y
{
Y(T y) {}
};

int main()
{
X x(3); //T is deduced to be int. OK in C++03 and C++0x;
Y y(3); //compiler error: missing template argument list. Error in 03 and 0x
}

lock_guard and thread aren't class templates. They have constructor templates though.

Constructor can't infer template argument from concept

That's just not the way deduction works in C++20.

For class template argument deduction (CTAD), the client class template needs to deduce two types (stream_t and serializer_t) and the constructor you have only uses one of those types, so the language has no idea where the other one could come from.

You can help this by providing a deduction guide for json_serializer:

template <stream S>
client(json_serializer<S>) -> client<S, json_serializer<S>>;

And now client c(s); works because you're telling it where the stream type comes from in this context. This could be generalized to any kind of serializer if you add some associated type for what stream a serializer is associated with.

As a guess:

template <typename S>
using stream_for = decltype(std::declval<S&>().serialize(42));

template <typename Serializer>
client(Serializer) -> client<stream_for<Serializer>, Serializer>;

Which would help if there was a unary concept for serializer rather than a binary one.


Note that in your concept definition, the requirement:

::stream<stream_t>;

Does not check that stream_t satisfies the stream concept. It checks that this is a valid expression. Which it would be regardless of whether the concept is satisfied or not (false is just as much a valid expression as true).

This needs to be:

requires ::stream<stream_t>;

Or, even better:

template <class Serializer, class Stream>
concept serializer_for =
stream<Stream>
&& requires (Serializer& s, any_t a) {
{ s.serialize(a) } -> std::same_as<Stream>;
};

Why can't constructors deduce template arguments?

Because nobody has specified how exactly that works. There is a current proposal to the standard committee to make it work. It also lists some of the difficulties:

http://open-std.org/JTC1/SC22/WG21/docs/papers/2015/n4471.html

Update: Here's the newest version of the proposal:

http://open-std.org/JTC1/SC22/WG21/docs/papers/2015/p0091r0.html

When a compiler can infer a template parameter?

Template parameters can be inferred for function templates when the parameter type can be deduced from the template parameters

So it can be inferred here:

template <typename T>
void f(T t);

template <typename T>
void f(std::vector<T> v);

but not here:

template <typename T>
T f() {
return T();
}

And not in class templates.

So the usual solution to your problem is to create a wrapper function, similar to the standard library function std::make_pair:

  template <class T>
class MyClass {
public:
MyClass(T t) {}
void print(){
std::cout<<"try MyClass"<<std::endl;
}
};

template <typename T>
MyClass<T> MakeMyClass(T t) { return MyClass<T>(t); }

and then call auto a = MakeMyClass(5); to instantiate the class.

Why compiler said candidate template ignored: couldn't infer template argument 'InputIterator'?

[The code in the question is neither minimal nor reproducible.]

The issue is that parameters of type cls<T>::type are not deducible. This is the exact pattern you have in:

template <typename InputIterator>
Vector(typename __isInputIterator<InputIterator>::__result, typename __isInputIterator<InputIterator>::__result, const allocator &);

You simply have to reformulate the constructor so that it will be deducible:

template <typename InputIterator, 
typename __isInputIterator<InputIterator>::__result* = nullptr>
Vector(InputIterator, InputIterator, const allocator & = allocator{});

This way, the InputIterator is deducible, and its validity is tested by the default parameter. This still won't do what you want, since in your partial code __isInputIterator<InputIterator>::__result is always defined to something. You want something that is SFINAE, i.e. defined only if this is an input iterator and undefined otherwise. You want something like std::enable_if:

template <typename InputIterator,
std::enable_if_t<__isInputIterator<InputIterator>::__result::value>* x = nullptr>
Vector(InputIterator, InputIterator, const allocator & = allocator{});


General comments regarding your code:

  1. You should not use names prefixed with double underscores (__) since these names are reserved for the compiler.
  2. The code is overly complicated for what it does. Look at the Stack Overflow question How to check if an arbitrary type is an iterator? for shorter ways to check if a type is an iterator.


This is code with minimum changes that works. It is ugly, and I won't recommend using it. It simply serves to fill up the gaps in the original question:

#include <memory>
#include <type_traits>
struct InputIterator {
constexpr static bool isInputIterator {true};
//...
};
struct __falseType
{
static constexpr bool value{false};
};
struct __trueType
{
static constexpr bool value{true};
};

template <typename, bool>
struct __InputIteratorInferringAuxiliary {
using __type = __falseType;
};
template <typename Iterator>
struct __InputIteratorInferringAuxiliary<Iterator, true> {
using __type = __trueType;
};

struct NotIterator {
static constexpr bool isInputIterator = false;
};
template <typename T>
struct __IteratorTraits {
using iteratorTag = NotIterator;
};

struct RandomAccessIterator {
static constexpr bool isInputIterator = true;

};

template <typename T>
struct __IteratorTraits<T *> {
using sizeType = unsigned long;
using differenceType = long;
using valueType = T;
using reference = valueType &;
using constReference = const valueType &;
using rightValueReference = valueType &&;
using pointer = valueType *;
using constPointer = const valueType *;
using iteratorTag = RandomAccessIterator;//isInputIterator tag in RandomAccessIterator class is true
};

template <typename Iterator>
struct __isInputIterator {
using __result = typename __InputIteratorInferringAuxiliary<Iterator,
__IteratorTraits<Iterator>::iteratorTag::isInputIterator
>::__type;
};
template <class T, class allocator=std::allocator<T>>
class Vector
{
public:
template <typename InputIterator,
std::enable_if_t<__isInputIterator<InputIterator>::__result::value>* x = nullptr>
Vector(InputIterator, InputIterator, const allocator & = allocator{});
T* begin();
T* end();
};
int main()
{
int arr[] {1, 2, 3};
Vector<int> vec(std::begin(arr), std::end(arr));//candidate template ignored: couldn't infer template argument 'InputIterator'
Vector<int> vec2(1,2);//candidate template ignored: couldn't infer template argument 'InputIterator'

}

c++ : using parameter pack in constructors?

With your current code, your instantiation of F<A> makes the Args template argument empty, which means the constructor only have the a argument, not args.

It seems that you want only the constructor to have a template parameter pack, not the whole class:

template<class T>
class F
{
public:
template<typename ... Args>
F(int a, Args ... args)
{
std::cout << a << std::endl;
T t(args...);
t.print();
}
};

C++ - Use inferred template argument from constructor as template argument throughtout class

No.

But you can make your entire class a template class, and create a factory function that does type deduction to generate your template class.

template<typename C>
class AnyClass {
public:
AnyClass( C* c );
std::vector<C> vec;
};
AnyClass<C> make_any_class( C* c ) {
return AnyClass<C>(c);
};


Related Topics



Leave a reply



Submit