c++template-argument-deductionc++23class-template

C++20/23 class template argument deduction: mix of explicit and deduced template arguments?


My template needs two types, one that I expect the caller to explicitly specify, the other which should be deducible by the compiler. Yet it requires me to specify it. Commenting out the foo2 line results in a clean build. Is there a way to get the foo2-type variable construction to work?

(The toy example program counts, in a variable of any math type, the number of allocations it makes for you of memory for the explicitly-supplied type.)

#include <cstdlib>

template<class T, class U> class Foo {
public:
    Foo( U* counter_in ) : counter( counter_in ) {};
    U* counter;
    T* Alloc() { *counter += 1; return (T*) malloc( sizeof(T) ); };
};

int iCounter = 0;
Foo<double, int> foo1( &iCounter );
Foo<double> foo2( &iCounter );

int main( int nArg, const char* apszArg[] ) {
    double* pd = foo1.Alloc();
    return 0;
}

Compiler output:

> g++ -std=c++23   -c -o t.o t.cpp
t.cpp:12:11: error: wrong number of template arguments (1, should be 2)
   12 | Foo<double> foo2( &iCounter );
      |           ^
t.cpp:3:34: note: provided for 'template<class T, class U> class Foo'
    3 | template<class T, class U> class Foo {
      |                                  ^~~
t.cpp:12:19: error: invalid conversion from 'int*' to 'int' [-fpermissive]
   12 | Foo<double> foo2( &iCounter );
      |                   ^~~~~~~~~
      |                   |
      |                   int*
t.cpp: In function 'int main(int, const char**)':
t.cpp:15:18: error: 'foo' was not declared in this scope; did you mean 'Foo'?
   15 |     double* pd = foo.Alloc();
      |                  ^~~
      |                  Foo
> g++ -v
Using built-in specs.
COLLECT_GCC=g++
COLLECT_LTO_WRAPPER=/usr/libexec/gcc/x86_64-redhat-linux/14/lto-wrapper
OFFLOAD_TARGET_NAMES=nvptx-none:amdgcn-amdhsa
OFFLOAD_TARGET_DEFAULT=1
Target: x86_64-redhat-linux
Configured with: ../configure --enable-bootstrap --enable-languages=c,c++,fortran,objc,obj-c++,ada,go,d,m2,lto --prefix=/usr --mandir=/usr/share/man --infodir=/usr/share/info --with-bugurl=http://bugzilla.redhat.com/bugzilla --enable-shared --enable-threads=posix --enable-checking=release --enable-multilib --with-system-zlib --enable-__cxa_atexit --disable-libunwind-exceptions --enable-gnu-unique-object --enable-linker-build-id --with-gcc-major-version-only --enable-libstdcxx-backtrace --with-libstdcxx-zoneinfo=/usr/share/zoneinfo --with-linker-hash-style=gnu --enable-plugin --enable-initfini-array --with-isl=/builddir/build/BUILD/gcc-14.0.1-20240411/obj-x86_64-redhat-linux/isl-install --enable-offload-targets=nvptx-none,amdgcn-amdhsa --enable-offload-defaulted --without-cuda-driver --enable-gnu-indirect-function --enable-cet --with-tune=generic --with-arch_32=i686 --build=x86_64-redhat-linux --with-build-config=bootstrap-lto --enable-link-serialization=1
Thread model: posix
Supported LTO compression algorithms: zlib zstd
gcc version 14.0.1 20240411 (Red Hat 14.0.1-0) (GCC)

Solution

  • CTAD is a whole or nothing.

    You might create a template make_Foo:

    template <typename T, typename U>
    Foo <T, U> make_Foo(U* u) { return Foo<T, U>(u); }
    

    and then

    auto foo2 = make_Foo<double>( &iCounter );