Я понимаю, почему возникает ошибка.
Рассмотрим следующий пример:
#include <cstdint>
#include <utility>
using UInt32 = std::uint32_t;
template<UInt32... s>
using Sequence = std::integer_sequence<UInt32, s...>;
template<UInt32... s, UInt32 x, UInt32... t>
constexpr auto foo(Sequence<s...>, Sequence<x, t...>) noexcept {
return 0;
}
int main() {
[[maybe_unused]] constexpr auto a = foo(Sequence<1, 2>{}, Sequence<3, 4, 5>{});
[[maybe_unused]] constexpr auto b = foo(Sequence<1, 2, 3>{}, Sequence<4, 5>{});
}
GCC 8.2.0 и Clang 7.0.0 успешно скомпилируют его.Но VC ++ (VS 2017) генерирует следующую ошибку:
Error C2664: 'int func<1,2,3,4,5>(std::integer_sequence<uint32_t,1,2>,std::integer_sequence<uint32_t,3,4,5>) noexcept': cannot convert argument 1 from 'std::integer_sequence<uint32_t,1,2,3>' to 'std::integer_sequence<uint32_t,1,2>'
Как видно из ошибки, компилятор попытался для b
использовать уже определенную функцию для a
из-за совпадениясписка аргументов шаблона функции и шаблонных типов, которые привели к сбою, потому что конечные типы параметров функции отличаются.Два других компилятора определили новую функцию с новыми типами для b
:
a: foo<1, 2, 3, 4, 5>(Sequence<1, 2>, Sequence<3, 4, 5>)
b: foo<1, 2, 3, 4, 5>(Sequence<1, 2, 3>, Sequence<4, 5>)
Таким образом, при вычислении Sequence<1>{} | Sequence<3, 4, 5>{}
произошла следующая ситуация:
constexpr auto operator|<1, 2, 3, 4, 5>(Sequence<1, 2>, Sequence<3, 4, 5>) noexcept {
if constexpr (find<3, 1, 2>()) { // false
return Sequence<1, 2>{} | Sequence<3, 4, 5>{};
} else {
return Sequence<1, 2, 3>{} | Sequence<4, 5>{}; // error C2679
}
}
Компилятор попыталсяиспользовать operator|<1, 2, 4, 5>(Sequence<1, 2>, Sequence<3, 4, 5>)
.
Теперь, зная суть проблемы, я смог переписать решение так, чтобы оно могло быть скомпилировано всеми тремя компиляторами:
using UInt32 = std::uint32_t;
template<UInt32... s>
struct Sequence {};
template<template<UInt32...> typename S, UInt32... s>
constexpr bool empty([[maybe_unused]] S<s...> a = {}) noexcept {
return sizeof...(s) == 0;
}
template<template<UInt32...> typename S, UInt32 x, UInt32... s>
constexpr UInt32 first([[maybe_unused]] S<x, s...> a = {}) noexcept {
return x;
}
template<template<UInt32...> typename S>
constexpr auto tail([[maybe_unused]] S<> a = {}) noexcept {
return S<>{};
}
template<template<UInt32...> typename S, UInt32 x, UInt32... s>
constexpr auto tail([[maybe_unused]] S<x, s...> a = {}) noexcept {
return S<s...>{};
}
template<UInt32 x, template<UInt32...> typename S>
constexpr bool find([[maybe_unused]] S<> a = {}) noexcept {
return false;
}
template<UInt32 x, template<UInt32...> typename S, UInt32 v, UInt32... s>
constexpr bool find([[maybe_unused]] S<v, s...> a = {}) noexcept {
if constexpr (v == x) {
return true;
} else {
return find<x>(S<s...>{});
}
}
template<UInt32... s, typename S2>
constexpr auto operator|(Sequence<s...>, S2) noexcept {
if constexpr (sizeof...(s) == 0) {
return S2{};
} else if constexpr (empty(S2{})) {
return Sequence<s...>{};
} else {
constexpr auto x = first(S2{});
if constexpr (find<x>(Sequence<s...>{})) {
return Sequence<s...>{} | tail(S2{});
} else {
return Sequence<s..., x>{} | tail(S2{});
}
}
}