The following code works:
template <typename T>
struct Foo
{
template <typename OStream>
static void default_print_function(OStream &, const T &);
template <typename OStream, typename PrintFunction>
void print(
OStream &,
const PrintFunction & = &Foo<T>::default_print_function<OStream>) const;
template <typename OStream>
void print(OStream &) const;
};
template <typename T>
template <typename OStream>
void Foo<T>::default_print_function(OStream & o, const T & value)
{
o << value;
}
template <typename T>
template <typename OStream, typename PrintFunction>
void Foo<T>::print(OStream & o, const PrintFunction & f)
const
{
T test_value = 123;
o << "TEST: ";
f(o, test_value);
}
template <typename T>
template <typename OStream>
void Foo<T>::print(OStream & o)
const
{
print(o, &default_print_function <OStream>);
}
#include <iostream>
int main()
{
Foo <int> foo;
foo.print(std::cerr, &Foo<int>::default_print_function<std::ostream>);
std::cerr << std::endl;
foo.print(std::cerr);
std::cerr << std::endl;
}
However, if I comment out the void print (OStream &) const overload:
template <typename T>
struct Foo
{
template <typename OStream>
static void default_print_function(OStream &, const T &);
template <typename OStream, typename PrintFunction>
void print(
OStream &,
const PrintFunction & = &Foo<T>::default_print_function<OStream>) const;
template <typename OStream>
void print(OStream &) const;
};
template <typename T>
template <typename OStream>
void Foo<T>::default_print_function(OStream & o, const T & value)
{
o << value;
}
template <typename T>
template <typename OStream, typename PrintFunction>
void Foo<T>::print(OStream & o, const PrintFunction & f)
const
{
T test_value = 123;
o << "TEST: ";
f(o, test_value);
}
/*template <typename T>
template <typename OStream>
void Foo<T>::print(OStream & o)
const
{
print(o, &default_print_function <OStream>);
}*/
#include <iostream>
int main()
{
Foo <int> foo;
foo.print(std::cerr, &Foo<int>::default_print_function<std::ostream>);
std::cerr << std::endl;
foo.print(std::cerr);
std::cerr << std::endl;
}
Then it won't compile:
test.cpp: In function ‘int main()’:
test.cpp:54:25: error: no matching function for call to ‘Foo<int>::print(std::ostream&)’
foo .print (std :: cerr);
^
test.cpp:8:7: note: candidate: template<class OStream, class PrintFunction> void Foo<T>::print(OStream&, const PrintFunction&) const [with OStream = OStream; PrintFunction = PrintFunction; T = int]
void print (
^~~~~
test.cpp:8:7: note: template argument deduction/substitution failed:
test.cpp:54:25: note: couldn't deduce template parameter ‘PrintFunction’
foo .print (std :: cerr);
The default value for the second argument for print is spelled in a way which seems to me to be exactly equivalent to the way it is spelled in the first invocation in the main function.
foo .print (std :: cerr, & Foo <int> :: default_print_function <std :: ostream>);
So why can the template argument be deduced in this invocation, but not be deduced in the default argument
const PrintFunction & = & Foo <T> :: default_print_function <OStream>
?
This is a non-deduced context:
cppreference
Non-deduced contexts:
...
4) A template parameter used in the parameter type of a function parameter that has a default argument that is being used in the call for which argument deduction is being done
EDIT (why it is a non-deduced context)
Because non-deduced context has a benefit: it allows the type to be used if it could be obtained from elsewhere.
Consider this example:
template<class T>
void foo(T, T = 1.0) {};
int main() {
foo(2);
}
This is valid code. But if it were not a non-deduced context, the deduction would conflict. It's somewhat reasonable to assume the programmer want to have a conversion instead of a failure.
On the other hand, the type is essentially given elsewhere (default argument that is given at declaration). For example, your code will be fine if you decltype to the template parameter:
template <typename OStream, typename PrintFunction = decltype(&Foo<T>::default_print_function<OStream>)>
All in all, we lose nothing to make it a non-deduced context. Moreover, we make the conversion of default argument type possible when the type is supplied elsewhere.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With