Search code examples
c++templatespartial-specializationdefault-parameters

Default parameter for partial specialization


What syntax I want to achieve on user side:

double a(1.), b(2.), deps(.1);
bool res1 = compare<double>()(a, b);        // works with default eps
bool res2 = compare<double, &deps>()(a, b);  // works with explicitly provided eps
float c(1.), d(1.). feps(.1);
bool res3 = compare<float>()(c, d);  // don't have default eps - must not compile
bool res4 = compare<float, &feps>()(c, d);   // works only with provided eps

What implementation for this I have now (not working because default parameters for partial specialization are not allowed):

extern double eps_double; // somewhere defined and initialized

template<typename T, const T* eps>
struct compare { // actually inherits std::binary_function
  bool operator()(const T& t1, const T& t2) {
    return t1 < t2 - *eps;
  }
};
template<const double* eps = &eps_double>
struct compare<double, eps> { // the same as in default implementation
};

I've tried with enable_if and wrapper classes that have static members, but static members can't be assigned to extern variables;

UPDATE: The actual problem is name equality for general struct and specialized struct. I don't know how to make it work without renaming:

// treats all explicitly passed eps and don't need default parameter
template<typename T, const T* eps>
struct compare_eps { // need another name! 
  bool operator()(const T& t1, const T& t2) {
    return t1 < t2 - *eps;
  }
};
// don't need default parameter either
// because we always know what eps to use for a concrete type
template<typename T>
struct compare { 
  // define nothing -> will not compile on types we don't have specialization for
}; 
template<>
struct compare<double> { 
  // use here eps_double hardcoded
}; 

Solution

  • I don't know why you think that this make sense

    compare<double, deps>
    

    You cannot make this work: Template arguments cannot be values of type double (they can be an lvalue of type double, but your template requires the address of a double, so that's off).

    You can use function templates to make your syntax work

    extern double eps_double;
    
    template<typename T>
    types::compare<T, &eps_double> compare(
      typename enable_if<is_same<T, double>>::type * = 0
    ) {
      return types::compare<T, &eps_double>(); 
    }
    
    template<typename T, const T *eps>
    types::compare<T, eps> compare() {
      return types::compare<T, eps>(); 
    }
    

    Alternatively, you can use class templates if you are up for some ugly hacks

    template<typename T, const T* eps = &eps_double>
    struct compare { 
      bool operator()(const T& t1, const T& t2) {
        return t1 < t2 - *eps;
      }
    };
    

    The default argument will not be used if you provide both arguments. If you provide only <double>, the default argument will be used and will work. If you only provide <float>, the default argument will be used too, but will not work.