Question
How to Check Whether a Templated Class Has a Member Function in C++
Question
In C++, is it possible to write a template that changes its behavior depending on whether a class defines a specific member function?
For example, suppose I want a function like this:
#include <string>
template<class T>
std::string optionalToString(T* obj)
{
if (FUNCTION_EXISTS(T::toString))
return obj->toString();
else
return "toString not defined";
}
The goal is:
- If
Thas atoString()member function, call it. - Otherwise, return a fallback string such as
"toString not defined".
How can the FUNCTION_EXISTS part be implemented in C++ templates?
Short Answer
By the end of this page, you will understand how C++ templates can detect whether a type provides a member function and then choose different behavior at compile time. You will learn the core idea behind SFINAE and the detection idiom, see modern and older C++ approaches, and build a practical optionalToString function that safely works with types that do or do not define toString().
Concept
In C++, templates are instantiated at compile time. That means the compiler generates code for a specific type only when that type is used. Because of this, C++ can make decisions based on what a type supports.
The core concept behind this question is compile-time detection of type capabilities. In generic programming, this is often called:
- SFINAE: Substitution Failure Is Not An Error
- detection idiom
- in modern C++, sometimes constraints or
if constexpr-based checks
The idea is simple:
- Try to form an expression such as
obj.toString()orstd::declval<T>().toString(). - If that expression is valid for type
T, use one implementation. - If it is not valid, let the compiler discard that path and use another one.
This matters because generic code often needs to work with many types:
- some types may support string conversion
- some may support iteration
- some may support comparison
- some may provide custom APIs
Instead of forcing every type to inherit from a base class, C++ templates can detect behavior directly from the type itself. This is a major strength of C++ generic programming.
For this problem, the main question is not just "does the type have a member named toString?" but usually:
- can
toString()be called? - does it take no arguments?
- does it return something convertible to ?
Mental Model
Think of a template like a tool that tries a key in a lock.
- If the key fits, the door opens and that version of the code is used.
- If the key does not fit, the compiler quietly tries another door instead of crashing immediately.
In this example:
- one door is the implementation for types that support
toString() - the other door is the fallback implementation
So the compiler is effectively asking:
"Can I call
toString()on this type?"
If yes, it uses that path. If no, it uses the fallback path.
This is a compile-time decision, not a runtime if statement.
Syntax and Examples
A modern C++ approach uses std::void_t, decltype, and partial specialization to detect whether T has a usable toString() member.
#include <iostream>
#include <string>
#include <type_traits>
#include <utility>
// Primary template: assume no toString()
template <typename, typename = std::void_t<>>
struct has_toString : std::false_type {};
// Specialization: valid if T has a callable toString()
template <typename T>
struct has_toString<T, std::void_t<decltype(std::declval<T>().toString())>>
: std::true_type {};
template <typename T>
std::string optionalToString(T* obj)
{
if constexpr {
obj->();
} {
;
}
}
{
{
;
}
};
{};
{
WithToString a;
WithoutToString b;
std::cout << (&a) << ;
std::cout << (&b) << ;
}
Step by Step Execution
Consider this small example:
#include <string>
#include <type_traits>
#include <utility>
template <typename, typename = std::void_t<>>
struct has_toString : std::false_type {};
template <typename T>
struct has_toString<T, std::void_t<decltype(std::declval<T>().toString())>>
: std::true_type {};
template <typename T>
std::string optionalToString(T* obj)
{
if constexpr (has_toString<T>::value) {
return obj->toString();
}
return "toString not defined";
}
struct User {
std::string toString() {
return "User object";
}
};
struct Point {};
Real World Use Cases
This pattern appears often in real C++ code, especially in generic libraries and reusable utilities.
Logging and debugging
A logging utility may prefer a type's custom toString() if available:
logger.write(optionalToString(&obj));
Serialization helpers
A serializer might check whether a type provides its own formatting or serialization method before using a default strategy.
Generic library code
Template-based libraries often detect capabilities such as:
begin()/end()for iterationsize()for container lengthreserve()for optimizationserialize()for custom persistenceswap()for efficient exchange
API wrappers
A wrapper around user-defined objects may support optional hooks such as:
validate()before saving datatoJson()before sending an API responsecleanup()before destruction
Real Codebase Usage
In real codebases, developers rarely write a fake macro like FUNCTION_EXISTS(...). Instead, they usually use one of these patterns.
1. Traits for capability checks
A trait like has_toString<T> is reusable and keeps the check in one place.
if constexpr (has_toString<T>::value) {
// custom path
} else {
// fallback
}
This is clean and easy to test.
2. Guarded generic functions
Developers often use detection to enable or disable overloads.
template <typename T>
auto stringify(const T& value) -> decltype(value.toString(), std::string()) {
return value.toString();
}
This style is common in older C++ code before if constexpr.
3. Validation of optional APIs
Libraries often check not just whether a function exists, but whether it has the right shape:
- correct name
Common Mistakes
1. Trying to use a normal runtime if
A regular if does not prevent compilation of invalid code in templates.
Broken idea:
template <typename T>
std::string optionalToString(T* obj) {
if (has_toString<T>::value)
return obj->toString();
else
return "toString not defined";
}
Why this can fail in older styles:
- both branches may still need to be valid during compilation
- if
Thas notoString(),obj->toString()can still cause an error
Use if constexpr in C++17+.
2. Checking only for the name, not the call
A member function may exist but not match what you need.
Examples:
- it may require parameters
- it may be overloaded
- it may return the wrong type
- it may be private
Better check the exact expression you intend to use:
Comparisons
| Approach | C++ Version | Best Use | Pros | Cons |
|---|---|---|---|---|
| SFINAE with overloads | C++11+ | Older codebases | Works without modern features | Harder to read |
Trait + std::void_t | C++17 commonly, possible earlier with custom void_t | Reusable detection | Clear and modular | Needs some template boilerplate |
if constexpr + trait | C++17+ | Branching inside one function | Very readable | Requires C++17 |
Concepts / requires | C++20+ | Clean constraints |
Cheat Sheet
// Detect whether T has callable toString()
template <typename, typename = std::void_t<>>
struct has_toString : std::false_type {};
template <typename T>
struct has_toString<T, std::void_t<decltype(std::declval<T>().toString())>>
: std::true_type {};
// Use it with if constexpr
template <typename T>
std::string optionalToString(T* obj)
{
if constexpr (has_toString<T>::value) {
return obj->toString();
} else {
return "toString not defined";
}
}
Quick rules
- Use
decltype(expr)to test whether an expression is valid. - Use
std::declval<T>()to form expressions without constructingT. - Use
std::void_tin detection traits.
FAQ
How do I check if a class has a member function in C++?
Use template metaprogramming techniques such as SFINAE, std::void_t, or C++20 requires expressions to test whether an expression like obj.toString() is valid.
Can I use a macro to detect whether a member function exists?
Not reliably. The C++ preprocessor does not understand class members or template substitution rules. Use traits or requires instead.
What is SFINAE in simple terms?
SFINAE means that if a template substitution creates an invalid type or expression in certain contexts, the compiler ignores that candidate instead of treating it as a hard error.
Why doesn't a normal if work for this?
Because template code must still be valid during compilation. A normal if does not discard invalid code. if constexpr does.
How do I check for toString() const specifically?
Use a const-qualified expression in the trait, such as:
decltype(std::declval<const T&>().toString())
Can I also check the return type of the member function?
Yes. Combine the existence check with or, in C++20, use a expression with .
Mini Project
Description
Build a small generic string formatter for C++ objects. The formatter should use an object's own toString() method when available and fall back to a default message when it is not. This demonstrates compile-time detection of member functions and safe branching in template code.
Goal
Create a reusable formatObject function that works with different types and automatically chooses the best available formatting behavior.
Requirements
- Create a trait that detects whether a type has a callable
toString()member function. - Write a generic
formatObjectfunction that returns the result oftoString()when available. - Return a fallback string when
toString()is not defined. - Test the function with at least one type that has
toString()and one type that does not. - Keep the implementation valid C++17 or later.
Keep learning
Related questions
Advantages of Brace Initialization in C++
Learn why C++ brace initialization is often clearer and safer than other object initialization styles, with examples and common pitfalls.
Basic Rules and Idioms for Operator Overloading in C++
Learn the core rules, syntax, and common idioms for operator overloading in C++, including member vs non-member operators.
C++ Aggregates, Trivial Types, Trivially Copyable Types, and PODs Explained
Learn what aggregates, trivial types, trivially copyable types, and PODs mean in C++, how they differ, and why they matter.