Widziałem typy, które mają odpowiadającą funkcję to_string()
, ale nie przeładowały operator<<()
. Tak więc, podczas wstawiania do streamowania, trzeba mieć << to_string(x)
, co jest pełne. Zastanawiam się, czy można napisać ogólną funkcję, która jest dostępna dla użytkowników operator<<()
, a jeśli nie, cofa się do << to_string()
.Fallback to to_string(), gdy operator <<() zawiedzie
Odpowiedz
SFINAE jest przesadą, użyj ADL.
Sztuką jest, aby upewnić się, że operator<<
jest dostępna, niekoniecznie jeden dostarczony przez definicji typu:
namespace helper {
template<typename T> std::ostream& operator<<(std::ostream& os, T const& t)
{
return os << to_string(t);
}
}
using helper::operator<<;
std::cout << myFoo;
Ta sztuczka jest powszechnie stosowany w rodzajowego kodu, który musi wybierać między std::swap<T>
i wyspecjalizowany Foo::swap(Foo::Bar&, Foo::Bar&)
.
Tak, jest to możliwe.
#include <iostream>
#include <sstream>
#include <string>
#include <type_traits>
struct streamy
{
};
std::ostream&
operator<<(std::ostream& os, const streamy& obj)
{
return os << "streamy [" << static_cast<const void *>(&obj) << "]";
}
struct stringy
{
};
std::string
to_string(const stringy& obj)
{
auto oss = std::ostringstream {};
oss << "stringy [" << static_cast<const void *>(&obj) << "]";
return oss.str();
}
template <typename T>
std::enable_if_t
<
std::is_same
<
std::string,
decltype(to_string(std::declval<const T&>()))
>::value,
std::ostream
>&
operator<<(std::ostream& os, const T& obj)
{
return os << to_string(obj);
}
int
main()
{
std::cout << streamy {} << '\n';
std::cout << stringy {} << '\n';
}
Ogólny operator<<
będzie dostępna tylko wtedy, gdy wyrażenie to_string(obj)
jest również wpisany do obj
w const T&
i ma wynik typu std::string
. Jak już się domyśliłeś w swoim komentarzu, faktycznie jest to SFINAE w pracy. Jeśli wyrażenie decltype
nie jest dobrze sformułowane, otrzymamy błąd substytucji, a przeciążenie zniknie.
Jednakże może to spowodować problemy z niejednoznacznymi przeciążeniami. Przynajmniej umieść swój kod rezerwowy operator<<
w swoim własnym namespace
i przeciągnij go lokalnie za pomocą deklaracji using
, gdy zajdzie taka potrzeba. Myślę, że lepiej będzie napisać nazwaną funkcję, która robi to samo.
namespace detail
{
enum class out_methods { directly, to_string, member_str, not_at_all };
template <out_methods> struct tag {};
template <typename T>
void
out(std::ostream& os, const T& arg, const tag<out_methods::directly>)
{
os << arg;
}
template <typename T>
void
out(std::ostream& os, const T& arg, const tag<out_methods::to_string>)
{
os << to_string(arg);
}
template <typename T>
void
out(std::ostream& os, const T& arg, const tag<out_methods::member_str>)
{
os << arg.str();
}
template <typename T>
void
out(std::ostream&, const T&, const tag<out_methods::not_at_all>)
{
// This function will never be called but we provide it anyway such that
// we get better error messages.
throw std::logic_error {};
}
template <typename T, typename = void>
struct can_directly : std::false_type {};
template <typename T>
struct can_directly
<
T,
decltype((void) (std::declval<std::ostream&>() << std::declval<const T&>()))
> : std::true_type {};
template <typename T, typename = void>
struct can_to_string : std::false_type {};
template <typename T>
struct can_to_string
<
T,
decltype((void) (std::declval<std::ostream&>() << to_string(std::declval<const T&>())))
> : std::true_type {};
template <typename T, typename = void>
struct can_member_str : std::false_type {};
template <typename T>
struct can_member_str
<
T,
decltype((void) (std::declval<std::ostream&>() << std::declval<const T&>().str()))
> : std::true_type {};
template <typename T>
constexpr out_methods
decide_how() noexcept
{
if (can_directly<T>::value)
return out_methods::directly;
else if (can_to_string<T>::value)
return out_methods::to_string;
else if (can_member_str<T>::value)
return out_methods::member_str;
else
return out_methods::not_at_all;
}
template <typename T>
void
out(std::ostream& os, const T& arg)
{
constexpr auto how = decide_how<T>();
static_assert(how != out_methods::not_at_all, "cannot format type");
out(os, arg, tag<how> {});
}
}
template <typename... Ts>
void
out(std::ostream& os, const Ts&... args)
{
const int dummy[] = {0, ((void) detail::out(os, args), 0)...};
(void) dummy;
}
Następnie użyj go w taki sposób.
int
main()
{
std::ostringstream nl {"\n"}; // has `str` member
out(std::cout, streamy {}, nl, stringy {}, '\n');
}
Funkcja decide_how
daje pełną elastyczność w podejmowaniu decyzji, jak wyjście danego rodzaju, nawet jeśli istnieje wiele dostępnych opcji. Jest również łatwy do przedłużenia. Na przykład niektóre typy mają funkcję składową str
zamiast funkcji bezpłatnej ADL find-able to_string
. (Właściwie to już zrobiłem.)
Funkcja detail::out
używa tag dispatching, aby wybrać odpowiednią metodę wyjściową.
Predykaty can_HOW
są implementowane za pomocą void_t
trick, które uważam za bardzo eleganckie.
Funkcja variadic out
używa modelu “for each argument” trick, który uważam za jeszcze bardziej elegancki.
Należy pamiętać, że kod to C++ 14 i będzie wymagać aktualnego kompilatora.
Czy to SFINAE? Więc kiedy kompiluje 'to_string (x)', 'return os << to_string (obj);' przeciążenie istnieje, a nie inaczej? Czy mogę użyć 'std :: enable_if' zamiast' std :: conditional'? – Lingxi
Myślę, że mając przeciążenie '<< to_string (x)' jeśli '<< x' nie skompilował byłoby miło, jeśli to w ogóle możliwe. – Lingxi
Tak, to jest SFINAE. Zobacz zaktualizowaną odpowiedź (szczególnie w odpowiedzi na drugi komentarz). Pomyślałem o używaniu 'std :: enable_if', ale nie mogłem znaleźć prostego rozwiązania, więc poszedłem z wprawdzie nieco mylącym' std :: conditional'. – 5gon12eder
Spróbuj
template <typename T>
void print_out(T t) {
print_out_impl(std::cout, t, 0);
}
template <typename OS, typename T>
void print_out_impl(OS& o, T t,
typename std::decay<decltype(
std::declval<OS&>() << std::declval<T>()
)>::type*) {
o << t;
}
template <typename OS, typename T>
void print_out_impl(OS& o, T t, ...) {
o << t.to_string();
}
Opierając się na odpowiedzi @MSalters (kredyty idą do niego), ten rozwiązuje mój problem i powinien udzielić kompletnej odpowiedzi.
#include <iostream>
#include <string>
#include <type_traits>
struct foo_t {};
std::string to_string(foo_t) {
return "foo_t";
}
template <class CharT, class Traits, class T>
typename std::enable_if<std::is_same<CharT, char>::value,
std::basic_ostream<CharT, Traits>&>::type
operator<<(std::basic_ostream<CharT, Traits>& os, const T& x) {
return os << to_string(x);
}
int main() {
std::cout << std::string{"123"} << std::endl;
std::cout << foo_t{} << std::endl;
}
- 1. Co oznacza operator Bash <<<?
- 2. operator << dla QString
- 3. Przeciążenia operatorów statycznych i użytkowników: std :: operator << i std :: ostream :: operator <<
- 4. C++ std :: stringstream operator << przeciążenia
- 5. Operator ~ <~ w PostgreSQL
- 6. Operator PHP <>
- 7. BOOST_CHECK nie skompilować operator << dla typów niestandardowych
- 8. wyjście Przeciążenie operator << dla klasy wydrukować krotki środku
- 9. przeciążony operator << na problemy konkatenacji ofstream
- 10. Operator Przeciążający C++; zbyt wiele parametrów dla << operacji
- 11. Używanie SFINAE do sprawdzania globalnego operatora <<?
- 12. Co to jest znak ampersand, gdy jest używany po nazwie klasy, np. Ostream i operator << (...)?
- 13. Przeciążenie << Operator dla zwiększenia Logowania Obiekt
- 14. Jaki jest << operator robi w C++?
- 15. operator << musi podjąć dokładnie jeden argument
- 16. jak operator przeciążenia << dla tablicy w C++?
- 17. Jak poprawnie przeciążyć operator << dla ostream?
- 18. IEnumerable <> to IList <>
- 19. Jaka jest różnica między std :: to_string, boost :: to_string i boost :: lexical_cast <std::string>?
- 20. operator << (ostream & os, ...) dla klasy szablonów
- 21. '<' operator jest zastrzeżony Błąd PowerShell
- 22. convert <vector><string> TO <vector><int> C++, Win32
- 23. Wyraźny operator na liście <string>
- 24. niepoprawny operator <podczas sortowania std :: list
- 25. jak mogę napisać klasy rejestratora z cout styl interfejsu (logger << "Błąd:" << endl << val;)
- 26. Korzystanie NInject, rozdzielając IEnumerable <T> zawiedzie podczas wstrzykiwania aa konstruktor Arg, ale nie wtedy, gdy robi Get <IEnumerable <T>>()
- 27. <% $, <% @, <% =, <% # ... o co chodzi?
- 28. przeciążanie operatora << dla tablic
- 29. C++ wiele definicji operatora <<
- 30. Przeciążenie operatora << dla tablic
Zgadzam się, że jest to prostsze w przypadku, gdy chcesz tylko 'operator <<' dla typu 'T' zdefiniowanego w jego' przestrzeni nazw 'lub' to_string (T) 'i nic więcej, co, oczywiście, jest tym, co OP ma poprosił o, więc +1. Jeśli potrzebujesz dalszej wysyłki, to nie zadziała. Ponadto komunikaty o błędach generowane przez to rozwiązanie mogą nie być tak pomocne, jak mogłyby być. – 5gon12eder
To jest miłe. Ale muszę przeciążyć 'operator <<()' dla każdego typu, który przeciął tylko 'to_string()'. Chcę uniknąć takiej żmudnej pracy. – Lingxi
@ling co? Jak myślisz, dlaczego musisz to robić? – Yakk