In order to show the problem intuitively, you can look directly at the 'UPDATE' section
#include <iostream>
template<int N>
struct state
{
static constexpr int value = N;
friend auto create(state<N>);
};
template<int N>
struct generate_state
{
friend auto create(state<N>) {
return state<N>{};
}
static constexpr int value = N;
};
template struct generate_state<1>;
template<int N, typename U = decltype(create(state<N - 1>{})) >
std::size_t getvalue(float,state<N>,int res = generate_state<N>::value) { #1
return N;
}
template<int N, typename U = decltype(create(state<N>{})) >
std::size_t getvalue(int, state<N>, int r = getvalue(0, state<N + 1>{})) { #2
return N;
}
int main(){
getvalue(0, state<1>{});
using type = decltype(create(state<2>{}));
}
Consider the above code,the result is logical.Beause every time invoke the getvalue
function will add the state
once ,It's the stateful metaprogramming.
But ,if change the getvalue(0, state<1>{});
to using t = decltype(getvalue(0, state<1>{}));
,the reuslt will be quite confused.
int main(){
using t = decltype(getvalue(0, state<1>{})); #3
using type = decltype(create(state<3>{}));
}
the above code can be complied in g++,It means the state
added twice,this result is quite confused.In order to explain why there is such a result.The following are my guess:
at #3,to decide which getvalue
to be used at the default arugment r
,Both #1
and #2
are considered,before instantiting #1
,generate_state<2>
should be instantited firstly ,so state<2>
was added, after that, no falis when #2 was substituted ,so #2 is the best match for state<2>
and then state<3>
was added.This process does not conform to the overloading rule of the function(in the case of normal,#1 and #2 only chose the one,the other is removed from the overload set). but it's not possible unless it's like this.why?
In order to show the complier process,add the static_assert to make the complier print some logs
main.cpp: In instantiation of ‘std::size_t getvalue(float, state<N>, int) [with int N = 2; U = state<1>; std::size_t = long unsigned int]’:
main.cpp:27:53: required from here
main.cpp:22:2: error: static assertion failed: #1
static_assert(!N, "#1");
^~~~~~~~~~~~~
main.cpp: In instantiation of ‘std::size_t getvalue(float, state<N>, int) [with int N = 3; U = state<2>; std::size_t = long unsigned int]’:
main.cpp:27:53: required from here
main.cpp:22:2: error: static assertion failed: #1
main.cpp: In instantiation of ‘std::size_t getvalue(int, state<N>, int) [with int N = 2; U = state<2>; std::size_t = long unsigned int]’:
main.cpp:27:53: required from here
main.cpp:28:2: error: static assertion failed: #2
static_assert(!N, "#2");
In order to simplify the problem,Decompose the code as following:
template<int N, typename U = decltype(create(state<N - 1>{})) >
std::size_t getvalue(float, state<N>, int res = generate_state<N>::value) {
static_assert(!N, "#1");
return N;
}
template<int N, typename U = decltype(create(state<N>{})) >
std::size_t getvalue(int, state<N>, int r = 0) {
static_assert(!N, "#2");
return N;
}
template<int N, typename U = state<N> >
std::size_t funproblem(int, state<N>, int r = getvalue(0, state<N + 1>{})) {
return N;
}
int main() {
using t = decltype(funproblem(0, state<1>{}));
}
main.cpp: In instantiation of ‘std::size_t getvalue(float, state<N>, int) [with int N = 2; U = state<1>; std::size_t = long unsigned int]’:
main.cpp:33:55: required from here
main.cpp:22:2: error: static assertion failed: #1
static_assert(!N, "#1");
^~~~~~~~~~~~~
main.cpp: In instantiation of ‘std::size_t getvalue(int, state<N>, int) [with int N = 2; U = state<2>; std::size_t = long unsigned int]’:
main.cpp:33:55: required from here
main.cpp:28:2: error: static assertion failed: #2
static_assert(!N, "#2");
both function template getvalue
are instantited,what's the hell?In case of normal,decltype(create(state<N>{}))
with N=2 will be substituted failed and will be removed from overload set,only the function template with the template parament U
of decltype(create(state<N - 1>{}))
with N=2 will be substituted successfully and to be instantited by the complier...
the quotes about function template with default arguments in standard document:
If a function template f is called in a way that requires a default argument to be used, the dependent names are looked up, the semantics constraints are checked, and the instantiation of any template used in the default argument is done as if the default argument had been an initializer used in a function template specialization with the same scope, the same template parameters and the same access as that of the function template f used at that point, except that the scope in which a closure type is declared ([expr.prim.lambda.closure]) – and therefore its associated namespaces – remain as determined from the context of the definition for the default argument. This analysis is called default argument instantiation. The instantiated default argument is then used as the argument of f
UPDATE:
The problem can be further simplified:
template<int N>
struct state
{
static constexpr int value = N;
friend auto create(state<N>);
};
template<int N>
struct generate_state
{
friend auto create(state<N>) {
return state<N>{};
}
static constexpr int value = N;
};
template struct generate_state<1>;
template<int N, typename U = decltype(create(state<N-1>{})) > #11
void getvalue(float, state<N>, int res = generate_state<N>::value) {
}
template<int N, typename U = decltype(create(state<N>{})) > #22
std::size_t getvalue(int, state<N>, int r = 0) {
return N;
}
int main() {
using t = decltype(getvalue(0, state<2>{}));
std::cout << typeid(t).name() << std::endl;
}
The gcc complier will print t = std::size_t
. It means the complier chose the #22
, but at this point of decltype(getvalue(0, state<2>{}))
, the defination of create(state<2>{})
does not exsit at all, #22
does not substitute successfully, it should be removed from the overload set
, accroding to the result that complier printed,a ctually it is not, how suprise it is!
If you change decltype(getvalue(0, state<2>{}));
to getvalue(0, state<2>{})
, #11
is the best match and to be instantited, this is conforming to logic, because create(state<2>{})
is not defined at this point, so #22
will be substituted failed, #11
is best matched.
What makes the result so confused? Does anyone know why? Is it a gcc bug or anything else?