My code is here:
#include <iostream>
#include <memory>
#include <queue>
template<typename TValue>
[[maybe_unused]]
constexpr auto t1(const std::queue<TValue> &value) -> void {
std::queue<TValue> temp = value;
while (!temp.empty()) {
std::cout << temp.front() << std::endl;
temp.pop();
}
}
template<typename TValue>
constexpr auto t1(const TValue &nm) -> void {
std::cout << nm << std::endl;
}
template<typename TValue>
constexpr auto t1(const std::shared_ptr<TValue> &nm) -> void {
t1<TValue>(*nm);
}
int main(int argc, char **argv) {
std::shared_ptr<const int> s_ptr = std::make_shared<const int>(7);
t1(s_ptr);
return 0;
}
This code fails to compile (https://godbolt.org/z/crvKb7rEz):
error C2338: static_assert failed: 'The C++ Standard forbids containers of const elements because allocator is ill-formed.'
I've tried to change template with shared_ptr like:
template<typename TValue>
constexpr auto t1(const std::shared_ptr<TValue> &nm) -> void {
const int temp = *nm;
t1<TValue>(temp);
}
It results in the same error. I've also tried to get the 'TValue' type:
template<typename TValue>
constexpr auto t1(const std::shared_ptr<TValue> &nm) -> void {
if constexpr (std::is_same_v<TValue, const int>){
static_assert(false, "??");
}
t1<TValue>(*nm);
}
static_assert is triggered.
That means 'TValue' is 'const std::string'.
Accidentally, I've removed <TValue> like this:
template<typename TValue>
constexpr auto t1(const std::shared_ptr<TValue> &nm) -> void {
t1(*nm);
}
Or:
template<typename TValue>
constexpr auto t1(const std::shared_ptr<TValue> &nm) -> void {
t1<std::remove_cv_t<TValue>>(*nm);
}
Also:
template<typename TValue>
constexpr auto t1(const TValue &nm) -> void {
std::cout << nm << std::endl;
}
template<typename TValue>
constexpr auto t1(const std::shared_ptr<TValue> &nm) -> void {
t1<TValue>(*nm);
}
int main(int argc, char **argv) {
std::shared_ptr<const int> s_ptr = std::make_shared<const int>(7);
t1(s_ptr);
return 0;
}
All of these work.
Why does the template wants to use std::queue overload when <TValue> euqal to const int? I except that it uses:
template<typename TValue>
constexpr auto t1(const TValue &nm) -> void {
std::cout << nm << std::endl;
}
The problem is that you're explicitly providing the template argument
TValuewhich results in an instantiation ofstd::deque<const std::string>.std::deque<T>requires a non-const, non-volatileT, as the failed static assertion says:- https://godbolt.org/z/PGY7nKW57
Minimal Reproducible Example
To illustrate what's going on, here's a simplified version of your problem (https://godbolt.org/z/zYPhhb5dq):
Solution
You can write
t1(*nm)instead. The difference here is thatstd::deque<const std::string>is never instantiated. Instead, substitution intot1(std::deque)would fail because*nmis aconst std::stringandTValueinstd::deque<TValue>cannot be deduced fromconst std::string.t1<std::remove_cv_t<TValue>>(*nm);also works (though you shouldn't write it) because you instantiatestd::deque<std::string>instead. This is valid, however, overload resolution will pickt1(const T&)because there is no implicit conversion fromstd::stringtostd::deque.You should write
t1(*nm);It's generally better to deduce template parameters, not provide them explicitly.Note on SFINAE-friendliness
The underlying issue is that
static_assertisn't SFINAE-friendly, i.e. it results in errors that come after substitution. In C++20, it is possible to use constraints instead ofstatic_assertwhile having the same template parameters:If this is done,
foo<const int>(0)is valid (https://godbolt.org/z/TWKxs99Yj).I don't believe it is legal for
std::dequeto have such constraints. If it had them, your code would work.